📄 ch04.htm
字号:
<tt> // the following function is called by every user-defined constructor</tt><tt> void init( size_t cap = DEFAULT_SIZE); </tt><tt>public:</tt><tt> string(const char * s);</tt><tt> string(size_t initial_capacity );</tt><tt> string();</tt><tt>//...other member functions and overloaded operators</tt><tt>};</tt><tt>void string::init( size_t cap)</tt><tt>{</tt><tt> pc = new char[cap];</tt><tt> capacity = cap;</tt><tt>}</tt><tt>string::string(const char * s)</tt><tt>{</tt><tt> size_t size = strlen (s);</tt><tt> init(size + 1); //make room for null terminating character</tt><tt> length = size;</tt><tt> strcpy(pc, s);</tt><tt>}</tt><tt>string::string(size_t initial_capacity )</tt><tt>{</tt><tt> init(initial_capacity);</tt><tt> length=0;</tt><tt>}</tt><tt>string::string()</tt><tt>{</tt><tt> init();</tt><tt> length = 0;</tt><tt>}</tt></pre><h3> <a name="Heading6">Is A Default Constructor Always Necessary?</a></h3><p>A class might have no default constructor. For example</p><pre><tt>class File</tt><tt>{</tt><tt>private:</tt><tt> string path;</tt><tt> int mode;</tt><tt>public:</tt><tt> File(const string& file_path, int open_mode);</tt><tt> ~File();</tt><tt>};</tt></pre><p>Class <tt>File</tt> has a user-defined constructor that takes two arguments. The existence of a user-defined constructor blocks the synthesis of an implicitly-declared default constructor. Because the programmer did not define a default constructor either, class <tt>File</tt> does not have a default constructor. A class with no default constructor limits its users to a narrower set of allowed uses. For instance, when an array of objects is instantiated, the default constructor -- and only the default constructor -- of each array member is invoked. Therefore, you cannot instantiate arrays thereof unless you use a complete initialization list: </p><pre><tt>File folder1[10]; //error, array requires default constructor</tt><tt>File folder2[2] = { File("f1", 1)}; //error, f2[1] still requires</tt><tt> //a default constructor</tt><tt>File folder3[3] = { File("f1", 1), File("f2",2), File("f3",3) }; //OK, </tt><tt> //fully initialized array</tt><tt>Similar difficulties arise when you attempt to store objects that have no default constructor in STL containers:#include <vector></tt><tt>using namespace std;</tt><tt>void f()</tt><tt>{</tt><tt> vector <File> fv(10); //error, File has no default constructor</tt><tt> vector <File> v; //OK</tt><tt> v.push_back(File("db.dat", 1)); //OK</tt><tt> v.resize(10); //error, File has no default constructor</tt><tt> v.resize(10, File("f2",2)); //OK</tt><tt>}</tt></pre><p> Was the lack of a default constructor in class <tt>File</tt> intentional? Maybe. Perhaps the implementer considered an array of <tt>File</tt> objects undesirable because each object in the array needs to somehow acquire its path and open mode. However, the lack of a default constructor </p><p>imposes restrictions that are too draconian for most classes.</p><h3> <a name="Heading7">Eligibility For STL Containment</a></h3><p>In order to qualify as an element in an STL container, an object must possess a copy constructor, an assignment operator, and a destructor as public members (more on this in Chapter 10, "STL and Generic Programming"). </p><p>A default constructor is also required for certain STL container operations, as you saw in the preceding example. </p><p>Many operating systems store the files in a directory as a linked list of file objects. By omitting a default constructor from <tt>File</tt>, the implementer severely compromises the capability of its users to implement a file system as a <tt>std::list<File></tt>.</p><p>For a class such as <tt>File</tt>, whose constructor must initialize its members with user-supplied values, it might still be possible to define a default constructor. Instead of supplying the necessary path and open mode as constructor arguments, a default constructor can read them from a sequential database file.</p><h3> <a name="Heading8">When Are Default Constructors Undesirable?</a></h3><p>Still, a default constructor can be undesirable in some cases. One such case is a singleton object. Because a singleton object must have one and only one instance, it is recommended that you block the creation of built-in arrays and containers of such objects by making the default constructor inaccessible. For example</p><pre><tt>#include<string></tt><tt>using namespace std;</tt><tt>int API_getHandle(); //system API function</tt><tt>class Application</tt><tt>{</tt><tt>private:</tt><tt> string name;</tt><tt> int handle;</tt><tt> Application(); // make default constructor inaccessible</tt><tt>public:</tt><tt> explicit Application(int handle);</tt><tt> ~Application();</tt><tt>};</tt><tt>int main()</tt><tt>{</tt><tt> Application theApp( API_getHandle() ); //ok</tt><tt> Application apps[10]; //error, default constructor is inaccessible</tt><tt>}</tt></pre><p>Class <tt>Application</tt> does not have a default constructor; therefore, it is impossible to create arrays and containers of <tt>Application</tt> objects. In this case, the lack of a default constructor is intentional (other implementation details are still required to ensure that a single instance -- and only a single instance -- of <tt>Application</tt> is created. However, making the default constructor inaccessible is one of these details).</p><h3> <a name="Heading9"> Constructors Of Fundamental Types</a></h3><p>Fundamental types such as <tt>char</tt>, <tt>int</tt>, and <tt>float</tt> have constructors, as do user-defined types. You can initialize a variable by explicitly invoking its default constructor:</p><pre><tt>int main()</tt><tt>{</tt><tt> char c = char();</tt><tt> int n = int ();</tt><tt> return 0;</tt><tt>}</tt></pre><p>The value that is returned by the explicit invocation of the default constructor of a fundamental type is equivalent to casting 0 to that type. In other words,</p><pre><tt>char c = char();</tt></pre><p>is equivalent to</p><pre><tt>char c = char(0);</tt></pre><p>Of course, it is possible to initialize a fundamental type with values other than 0:</p><pre><tt>float f = float (0.333);</tt><tt>char c = char ('a');</tt></pre><p>Normally, you use the shorter notation:</p><pre><tt>char c = 'a';</tt><tt>float f = 0.333;</tt></pre><p>However, this language extension enables uniform treatment in templates for fundamental types and user-defined types. Fundamental types that are created on the free store using the operator <tt>new</tt> can be initialized in a similar manner:</p><pre><tt>int *pi= new int (10); </tt><tt>float *pf = new float (0.333);</tt></pre><h3> <a name="Heading10">explicit Constructors</a></h3><p>A constructor that takes a single argument is, by default, an implicit conversion operator, which converts its argument to an object of its class (see also Chapter 3, "Operator Overloading"). Examine the following concrete example:</p><pre><tt>class string</tt><tt>{</tt><tt>private:</tt><tt> int size;</tt><tt> int capacity;</tt><tt> char *buff;</tt><tt>public:</tt><tt> string();</tt><tt> string(int size); // constructor and implicit conversion operator</tt><tt> string(const char *); // constructor and implicit conversion operator</tt><tt> ~string();</tt><tt>};</tt></pre><p>Class <tt>string</tt> has three constructors: a default constructor, a constructor that takes <tt>int</tt>, and a constructor that constructs a string from <tt>const char *</tt>. The second constructor is used to create an empty <tt>string</tt> object with an initial preallocated buffer at the specified size. However, in the case of class <tt>string</tt>, the automatic conversion is dubious. Converting an <tt>int</tt> into a string object doesn't make sense, although this is exactly what this constructor does. Consider the following:</p><pre><tt>int main()</tt><tt>{</tt><tt> string s = "hello"; //OK, convert a C-string into a string object</tt><tt> int ns = 0;</tt><tt> s = 1; // 1 oops, programmer intended to write ns = 1,</tt><tt>}</tt></pre><p>In the expression <tt>s= 1;</tt>, the programmer simply mistyped the name of the variable <tt>ns,</tt> typing <tt>s</tt> instead. Normally, the compiler detects the incompatible types and issues an error message. However, before ruling it out, the compiler first searches for a user-defined conversion that allows this expression; indeed, it finds the constructor that takes <tt>int</tt>. Consequently, the compiler interprets the expression <tt>s= 1;</tt> as if the programmer had written</p><pre><tt>s = string(1);</tt></pre><p>You might encounter a similar problem when calling a function that takes a <tt>string</tt> argument. The following example can either be a cryptic coding style or simply a programmer's typographical error. However, due to the implicit conversion constructor of class <tt>string</tt>, it will pass unnoticed:</p><pre><tt>int f(string s);</tt><tt>int main()</tt><tt>{</tt><tt> f(1); // without a an explicit constructor,</tt><tt> //this call is expanded into: f ( string(1) );</tt><tt> //was that intentional or merely a programmer's typo?</tt><tt>}</tt></pre><p></p><p>'In order to avoid such implicit conversions, a constructor that takes one argument needs to be declared <tt>explicit</tt>:</p><pre><tt>class string</tt><tt>{</tt><tt>//...</tt><tt>public:</tt><tt> explicit string(int size); // block implicit conversion</tt><tt> string(const char *); //implicit conversion</tt><tt> ~string();</tt><tt>};</tt></pre><p>An <tt>explicit</tt> constructor does not behave as an implicit conversion operator, which enables the compiler to catch the typographical error this time:</p><pre><tt>int main()</tt><tt>{</tt><tt> string s = "hello"; //OK, convert a C-string into a string object</tt><tt> int ns = 0;</tt><tt> s = 1; // compile time error ; this time the compiler catches the typo</tt><tt>}</tt></pre><p>Why aren't all constructors automatically declared <tt>explicit</tt>? Under some conditions, the automatic type conversion is useful and well behaved. A good example of this is the third constructor of <tt>string</tt>:</p><pre><tt>string(const char *);</tt></pre><p>The implicit type conversion of <tt>const char *</tt> to a string object enables its users to write the following:</p><pre><tt> string s;</tt><tt> s = "Hello";</tt></pre><p>The compiler implicitly transforms this into</p><pre><tt> string s;</tt><tt> //pseudo C++ code:</tt><tt> s = string ("Hello"); //create a temporary and assign it to s</tt></pre><p>On the other hand, if you declare this constructor <tt>explicit</tt>, you have to use explicit type conversion:</p><pre><tt>class string</tt><tt>{</tt><tt>//...</tt><tt>public:</tt><tt> explicit string(const char *);</tt><tt>};</tt><tt>int main()</tt><tt>{</tt><tt> string s;</tt><tt> s = string("Hello"); //explicit conversion now required</tt><tt> return 0;</tt>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -