B. Stroustrup - The C++ Programming Language (794319), страница 98
Текст из файла (страница 98)
};is called a class definition; it defines a type called X. For historical reasons, a class definition isoften referred to as a class declaration. Also, like declarations that are not definitions, a class definition can be replicated in different source files using #include without violating the one-definitionrule (§15.2.3).By definition, a struct is a class in which members are by default public; that is,struct S { /* ... */ };is simply shorthand forclass S { public: /* ... */ };These two definitions of S are interchangeable, though it is usually wise to stick to one style.Which style you use depends on circumstances and taste. I tend to use struct for classes that I thinkof as ‘‘just simple data structures.’’ If I think of a class as ‘‘a proper type with an invariant,’’ I useclass. Constructors and access functions can be quite useful even for structs, but as a shorthandrather than guarantors of invariants (§2.4.3.2, §13.4).By default, members of a class are private:class Date1 {int d, m, y;// private by defaultpublic:Date1(int dd, int mm, int yy);void add_year(int n);// add n years};Section 16.2.4classand struct455However, we can also use the access specifier private: to say that the members following are private,just as public: says that the members following are public:struct Date2 {private:int d, m, y;public:Date2(int dd, int mm, int yy);void add_year(int n);// add n years};Except for the different name, Date1 and Date2 are equivalent.It is not a requirement to declare data first in a class.
In fact, it often makes sense to place datamembers last to emphasize the functions providing the public user interface. For example:class Date3 {public:Date3(int dd, int mm, int yy);void add_year(int n);// add n yearsprivate:int d, m, y;};In real code, where both the public interface and the implementation details typically are moreextensive than in tutorial examples, I usually prefer the style used for Date3.Access specifiers can be used many times in a single class declaration. For example:class Date4 {public:Date4(int dd, int mm, int yy);private:int d, m, y;public:void add_year(int n);// add n years};Having more than one public section, as in Date4, tends to be messy, though, and might affect theobject layout (§20.5).
So does having more than one private section. However, allowing manyaccess specifiers in a class is useful for machine-generated code.16.2.5 ConstructorsThe use of functions such as init() to provide initialization for class objects is inelegant and errorprone.
Because it is nowhere stated that an object must be initialized, a programmer can forget todo so – or do so twice (often with equally disastrous results). A better approach is to allow the programmer to declare a function with the explicit purpose of initializing objects. Because such afunction constructs values of a given type, it is called a constructor. A constructor is recognized byhaving the same name as the class itself. For example:456ClassesChapter 16class Date {int d, m, y;public:Date(int dd, int mm, int yy);// ...};// constructorWhen a class has a constructor, all objects of that class will be initialized by a constructor call.
Ifthe constructor requires arguments, these arguments must be supplied:Date today = Date(23,6,1983);Date xmas(25,12,1990);// abbreviated formDate my_birthday;// error : initializer missingDate release1_0(10,12);// error : third argument missingSince a constructor defines initialization for a class, we can use the {}-initializer notation:Date today = Date {23,6,1983};Date xmas {25,12,1990};// abbreviated formDate release1_0 {10,12};// error : third argument missingI recommend the {} notation over the () notation for initialization because it is explicit about what isbeing done (initialization), avoids some potential mistakes, and can be used consistently (§2.2.2,§6.3.5).
There are cases where () notation must be used (§4.4.1, §17.3.2.1), but they are rare.By providing several constructors, we can provide a variety of ways of initializing objects of atype. For example:class Date {int d, m, y;public:// ...Date(int, int, int);Date(int, int);Date(int);Date();Date(const char∗);// day, month, year// day, month, today’s year// day, today’s month and year// default Date: today// date in string representation};Constructors obey the same overloading rules as do ordinary functions (§12.3). As long as the constructors differ sufficiently in their argument types, the compiler can select the correct one for a use:Date today {4};Date july4 {"July 4, 1983"};Date guy {5,11};Date now;Date start {};// 4, today.m, today.y// 5, November, today.y// default initialized as today// default initialized as todayThe proliferation of constructors in the Date example is typical.
When designing a class, a programmer is always tempted to add features just because somebody might want them. It takes morethought to carefully decide what features are really needed and to include only those. However,that extra thought typically leads to smaller and more comprehensible programs. One way ofSection 16.2.5Constructors457reducing the number of related functions is to use default arguments (§12.2.5). For Date, each argument can be given a default value interpreted as ‘‘pick the default: today.’’class Date {int d, m, y;public:Date(int dd =0, int mm =0, int yy =0);// ...};Date::Date(int dd, int mm, int yy){d = dd ? dd : today.d;m = mm ? mm : today.m;y = yy ? yy : today.y;// check that the Date is valid}When an argument value is used to indicate ‘‘pick the default,’’ the value chosen must be outsidethe set of possible values for the argument.
For day and month, this is clearly so, but for year, zeromay not be an obvious choice. Fortunately, there is no year zero on the European calendar; 1AD(year==1) comes immediately after 1BC (year==−1).Alternatively, we could use the default values directly as default arguments:class Date {int d, m, y;public:Date(int dd =today.d, int mm =today.m, int yy =today.y);// ...};Date::Date(int dd, int mm, int yy){// check that the Date is valid}However, I chose to use 0 to avoid building actual values into Date’s interface.
That way, we havethe option to later improve the implementation of the default.Note that by guaranteeing proper initialization of objects, the constructors greatly simplify theimplementation of member functions. Given constructors, other member functions no longer haveto deal with the possibility of uninitialized data (§16.3.1).16.2.6 explicit ConstructorsBy default, a constructor invoked by a single argument acts as an implicit conversion from its argument type to its type. For example:complex<double> d {1};// d=={1,0} (§5.6.2)458ClassesChapter 16Such implicit conversions can be extremely useful. Complex numbers are an example: if we leaveout the imaginary part, we get a complex number on the real axis. That’s exactly what mathematicsrequires.
However, in many cases, such conversions can be a significant source of confusion anderrors. Consider Date:void my_fct(Date d);void f(){Date d {15};// ...my_fct(15);d = 15;// ...}// plausible: x becomes {15,today.m,today.y}// obscure// obscureAt best, this is obscure. There is no clear logical connection between the number 15 and a Dateindependently of the intricacies of our code.Fortunately, we can specify that a constructor is not used as an implicit conversion.
A constructor declared with the keyword explicit can only be used for initialization and explicit conversions.For example:class Date {int d, m, y;public:explicit Date(int dd =0, int mm =0, int yy =0);// ...};Date d1 {15};Date d2 = Date{15};Date d3 = {15};Date d4 = 15;// OK: considered explicit// OK: explicit// error: = initialization does not do implicit conversions// error: = initialization does not do implicit conversionsvoid f(){my_fct(15);my_fct({15});my_fct(Date{15});// ...}// error: argument passing does not do implicit conversions// error: argument passing does not do implicit conversions// OK: explicitAn initialization with an = is considered a copy initialization.
In principle, a copy of the initializeris placed into the initialized object. However, such a copy may be optimized away (elided), and amove operation (§3.3.2, §17.5.2) may be used if the initializer is an rvalue (§6.4.1). Leaving outthe = makes the initialization explicit. Explicit initialization is known as direct initialization.By default, declare a constructor that can be called with a single argument explicit.
You need agood reason not to do so (as for complex). If you define an implicit constructor, it is best to document your reason or a maintainer may suspect that you were forgetful (or ignorant).Section 16.2.6explicitConstructors459If a constructor is declared explicit and defined outside the class, that explicit cannot be repeated:class Date {int d, m, y;public:explicit Date(int dd);// ...};Date::Date(int dd) { /* ... */ }explicit Date::Date(int dd) { /* ... */ }// OK// errorMost examples where explicit is important involve a single constructor argument. However, explicitcan also be useful for constructors with zero or more than one argument.
For example:struct X {explicit X();explicit X(int,int);};X x1 = {};X x2 = {1,2};// error : implicit// error : implicitX x3 {};X x4 {1,2};// OK: explicit// OK: explicitint f(X);int i1 = f({});int i2 = f({1,2});// error : implicit// error : implicitint i3 = f(X{});int i4 = f(X{1,2});// OK: explicit// OK: explicitThe distinction between direct and copy initialization is maintained for list initialization(§17.3.4.3).16.2.7 In-Class InitializersWhen we use several constructors, member initialization can become repetitive. For example:class Date {int d, m, y;public:Date(int, int, int);Date(int, int);Date(int);Date();Date(const char∗);// ...};// day, month, year// day, month, today’s year// day, today’s month and year// default Date: today// date in string representation460ClassesChapter 16We can deal with that by introducing default arguments to reduce the number of constructors(§16.2.5).
Alternatively, we can add initializers to data members:class Date {int d {today.d};int m {today.m};int y {today.y};public:Date(int, int, int);Date(int, int);Date(int);Date();Date(const char∗);// ...// day, month, year// day, month, today’s year// day, today’s month and year// default Date: today// date in string representationNow, each constructor has the d, m, and y initialized unless it does it itself.