Constructor
Classes control default initialization by defining a special constructor, known as the default constructor. The default constructor is one that takes no arguments.
If our class does not explicitly define any constructors, the compiler will implicitly define the default constructor for us. The compiler-generated constructor is known as the synthesized default constructor. For most classes, this synthesized constructor initializes each data member of the class as follows:
- If there is an in-class initializer, use it to initialize the member.
- Otherwise, default-initialize the member.
The compiler generates a default constructor automatically only if a class declares no constructors.
class A
{
public:
A()=default;
};
First, note that this constructor defines the default constructor because it takes no arguments1. We are defining this constructor only because we want to provide other constructors as well as the default constructor. We want this constructor to do exactly the same work as the synthesized version we had been using.
Under the new standard, if we want the default behavior, we can ask the compiler to generate the constructor for us by writing = default
after the parameter list. The = default
can appear with the declaration inside the class body or on the definition outside the class body. Like any other function, if the = default
appears inside the class body, the default constructor will be inlined; if it appears on the definition outside the class, the member will not be inlined by default.
几种构造函数的签名(包含赋值运算符):
// cf. https://learn.microsoft.com/zh-cn/cpp/cpp/constructors-cpp?view=msvc-170
class Box2
{
public:
Box2() = delete; // default ctor
Box2(const Box2& other) = default; // copy ctor
Box2& operator=(const Box2& other) = default; // copy assignment
Box2(Box2&& other) = default; // move ctor
Box2& operator=(Box2&& other) = default; // move assignment
//...
};
Constructor Initializer List
struct Vector3
{
Vector3()=default;
Vector3(float _x, float _y, float _z)
: x(_x), y(_y), z(_z) {} // ctor initializer list
int x, y, z;
}
这就叫Constructor Initializer List.
It is usually best for a constructor to use an in-class initializer if one exists and gives the member the correct value.
Constructors should not override in-class initializers except to use a different initial value. If you can’t use in-class initializers, each constructor should explicitly initialize every member of built-in type.
Revisited
When we define variables, we typically initialize them immediately rather than defining them and then assigning to them:
string foo = "Hello World!"; // define and initialize
string bar; // default initialized to the empty string
bar = "Hello World!"; // assign a new value to bar
Exactly the same distinction between initialization and assignment applies to the data members of objects. If we do not explicitly initialize a member in the constructor initializer list, that member is default initialized before the constructor body starts executing.
As a result, see below:
We must use the constructor initializer list to provide values for members that are const, reference, or of a class type that does not have a default constructor.
Delegating Constructors (since c++11)
A delegating constructor uses another constructor from its own class to perform its initialization. It is said to “delegate” some (or all) of its work to this other constructor. For example:
class Sales_data {
public:
// nondelegating constructor initializes members from corresponding arguments
Sales_data(std::string s, unsigned cnt, double price):
bookNo(s), units_sold(cnt), revenue(cnt*price) { }
// remaining constructors all delegate to another constructor
Sales_data(): Sales_data("", 0, 0) {}
Sales_data(std::string s): Sales_data(s, 0,0) {}
Sales_data(std::istream &is): Sales_data()
{ read(is, *this); }
// other members as before
};
Implicit Class-Type Conversions
Every constructor that can be called with a single argument defines an implicit conversion to a class type. Such constructors are sometimes referred to as converting constructors.
A constructor that can be called with a single argument defines an implicit conversion from the constructor’s parameter type to the class type.
Note: Only One Class-Type Conversion Is Allowed. 只能做一步转换,否则调不到对应构造函数。如下例
#include <iostream>
#include <vector>
#include <string>
using namespace std;
struct A
{
A(string _s): s(_s) { cout << "A::ctor called\n"; }
string s;
};
int main()
{
vector<A> a;
a.push_back(string("abc")); // valid, implicit call A::A(string)
a.push_back("def"); // invalid, need two conversion, char*->string, string->A
return 0;
}
如果不想要这种转换,使用explicit
关键字阻止之。
Suppressing Implicit Conversions Defined by Constructors
We can prevent the use of a constructor in a context that requires an implicit conversion by declaring the constructor as explicit
:
struct A
{
explicit A(string _s): s(_s) { cout << "A::ctor called\n"; }
string s;
};
int main()
{
vector<A> a;
a.push_back(string("abc")); // error, A::A(string) is now explicit
return 0;
}
意思是用explict
修饰的构造函数,必须显式的调用。
The explicit
keyword is meaningful only on constructors that can be called with a single argument.
When a constructor is declared explicit, it can be used only with the direct form of initialization (§ 3.2.1, p. 84). Moroever, the compiler will not use this constructor in an automatic conversion.
Member initialization
class A
{
int a = 1; // 简单数据成员的in-class initializer
vector<int> b = vector<int>(1, 2, 3); // 复杂数据成员的in-class intializer
vector<int> c {4, 5, 6}; // since c++11
vector<int> d = {7, 8, 9}; // not recommended
};
As we’ve seen, in-class initializers must use either the = form of initialization (which we used when we initialized the the data members of Screen) or the direct form of initialization using curly braces (as we do for screens).
When we provide an in-class initializer, we must do so following an = sign or inside braces.
原本类的数据成员的定义和初始化是分开的,成员定义在类中,成员初始化由构造函数负责。而现在提供一种叫做”in-class initializer”的方式,它的语法如上所述。
所以,不要纳闷为啥在成员声明中不能直接调用构造函数。
struct Vector3
{
Vector3()=default;
Vector3(float _x, float _y, float _z)
: x(_x), y(_y), z(_z) {}
int x, y, z;
};
struct A
{
Vector3 pos {1,2,3};
Vector3 velocity = Vector3(4,5,6);
Vector3 v(7,8,9); // invalid syntax
};
int main()
{
Vector3 v(1,2,3); // valid, 定义变量v
return 0;
}
因为你在试图使用in-class initializer,但你没有遵循它的格式(a = sign or inside braces)。
Constructors, Destructors, and Assignment Operators
If you don’t declare them yourself, compilers will declare their own versions of a copy constructor, a copy assignment operator, and a destructor. Furthermore, if you declare no constructors at all, compilers will also declare a default constructor for you. All these functions will be both public
and inline
.
As a result, if you write
class Empty{};
it’s essentially the same as if you’d written this:
class Empty {
public:
Empty() { ... } // default constructor
Empty(const Empty& rhs) { ... } // copy constructor
~Empty() { ... } // destructor — see below
// for whether it’s virtual
Empty& operator=(const Empty& rhs) { ... } // copy assignment operator
};
These functions are generated only if they are needed, the following code will cause each function to be generated:
Empty e1; // default constructor;
// destructor
Empty e2(e1); // copy constructor
e2 = e1; // copy assignment operator
Things to Remember ✦ Compilers may implicitly generate a class’s default constructor, copy constructor, copy assignment operator, and destructor.
C++98有4个特殊非静态成员函数:
- 默认构造函数:没有任何其他构造函数时默认提供
- 拷贝构造函数:用户不提供时默认提供
- 拷贝赋值运算符:用户不提供时默认提供
- 析构函数:用户不提供时默认提供
三法则由是呼之而出:
三法则
如果某个类需要用户定义的析构函数,用户定义的拷贝构造函数或用户定义的拷贝赋值运算符,则它几乎肯定三者全部都需要。
调用时机(from cpprefernce)
COPY CONSTRUCTOR
The copy constructor is called whenever an object is initialized (by direct-initialization or copy-initialization) from another object of the same type (unless overload resolution selects a better match or the call is elided), which includes
- initialization:
T a = b;
orT a(b);
, where b is of typeT
; - function argument passing:
f(a);
, where a is of typeT
andf
is voidf(T t)
; - function return:
return a;
inside a function such asT f()
, where a is of typeT
, which has no move constructor.
MOVE CONSTRUCTOR
The move constructor is typically called when an object is initialized (by direct-initialization or copy-initialization) from rvalue (xvalue or prvalue) (until C++17)xvalue (since C++17) of the same type, including
- initialization:
T a = std::move(b);
orT a(std::move(b));
, where b is of typeT
; - function argument passing:
f(std::move(a));
, where a is of typeT
andf
is voidf(T t)
; - function return:
return a;
inside a function such asT f()
, where a is of typeT
which has a move constructor.
COPY ASSIGNMENT
The copy assignment operator is called whenever selected by overload resolution, e.g. when an object appears on the left side of an assignment expression.
MOVE ASSIGNMENT
The move assignment operator is called whenever it is selected by overload resolution, e.g. when an object appears on the left-hand side of an assignment expression, where the right-hand side is an rvalue of the same or implicitly convertible type.
注意
T a = b;
是一个initialization expression而不是assignment expression.
Prevent copies for your object
- 「将构造函数显式声明为私有」By declaring a member function explicitly, you prevent compilers from generating their own version, and by making the function
private
, you keep people from calling it. - 「将copy ctor声明为
delete
(since c++11)」。
class HomeForSale {
public:
...
HomeForSale(const HomeForSale&)=delete;
HomeForSale& operator=(const HomeForSale&)=delete;
};
第一种方法常见用法:
class HomeForSale {
public:
...
private:
...
HomeForSale(const HomeForSale&); // declarations only
HomeForSale& operator=(const HomeForSale&);
};
With the above class definition, compilers will thwart client attempts to copy HomeForSale
objects, and if you inadvertently try to do it in a member or a friend function, the linker will complain.
It’s possible to move the link-time error up to compile time (always a good thing — earlier error detection is better than later) by declaring the copy constructor and copy assignment operator private
not in HomeForSale
itself, but in a base class specifically designed to prevent copying. The base class is simplicity itself:
class Uncopyable {
protected: // allow construction
Uncopyable() {} // and destruction of
~Uncopyable() {} // derived objects...
private:
Uncopyable(const Uncopyable&); // ...but prevent copying
Uncopyable& operator=(const Uncopyable&);
};
To keep HomeForSale
objects from being copied, all we have to do now is inherit from Uncopyable
:
class HomeForSale: private Uncopyable { // class no longer
... // declares copy ctor or
}; // copy assign. operator
This works, because compilers will try to generate a copy constructor and a copy assignment operator if anybody — even a member or friend function — tries to copy a HomeForSale
object. As Item 12 explains, the compiler-generated versions of these functions will try to call their base class counterparts, and those calls will be rejected, because the copying operations are private
in the base class.
Declare destructors virtual
in polymorphic base classes
see also When should my destructor be virtual.
Deleting a derived class object through a pointer to a base class with a non-
virtual
destructor results in undefined behavior.
Things to Remember
✦ Polymorphic base classes should declare virtual
destructors. If a class has any virtual
functions, it should have a virtual
destructor.
✦ Classes not designed to be base classes or not designed to be used polymorphically should not declare virtual
destructors. 会增加空间开销
不要在析构函数里面抛异常! Things to Remember ✦ Destructors should never emit exceptions. If functions called in a destructor may throw, the destructor should catch any exceptions, then swallow them or terminate the program. ✦ If class clients need to be able to react to exceptions thrown during an operation, the class should provide a regular (i.e., non-destructor) function that performs the operation.
Handle assignment to self in operator=
An assignment to self occurs when an object is assigned to itself:
class Widget { ... };
Widget w;
...
w = w; // assignment to self
This looks silly, but it’s legal, so rest assured that clients will do it. Besides, assignment isn’t always so recognizable. For example,
a[i] = a[j]; // potential assignment to self
is an assignment to self if i
and j
have the same value, and
*px = *py; // potential assignment to self
is an assignment to self if px
and py
happen to point to the same thing.
Suppose you create a class that holds a raw pointer to a dynamically allocated bitmap
class Bitmap { ... };
class Widget {
...
private:
Bitmap *pb; // ptr to a heap-allocated object
};
Here’s an implementation of operator=
that looks reasonable on the surface but is unsafe in the presence of assignment to self. (It’s also not exception-safe, but we’ll deal with that in a moment.)
Widget&
Widget::operator=(const Widget& rhs) // unsafe impl. of operator=
{
delete pb; // stop using current bitmap
pb = new Bitmap(*rhs.pb); // start using a copy of rhs’s bitmap
return *this;
}
The self-assignment problem here is that inside operator=
, *this
(the target of the assignment) and rhs
could be the same object. When they are, the delete not only destroys the bitmap for the current object, it destroys the bitmap for rhs
, too. At the end of the function, the Widget — which should not have been changed by the assignment to self — finds itself holding a pointer to a deleted object!
The traditional way to prevent this error is to check for assignment to self via an identity test at the top of operator=
:
Widget& Widget::operator=(const Widget& rhs)
{
if (this == &rhs) return *this; // identity test: if a self-assignment,
// do nothing
delete pb;
pb = new Bitmap(*rhs.pb);
return *this;
References
- C++ Primer, 3rd; ch7
- Effective C++, 3rd Edition; ch2
Footnotes
-
A constructor that supplies default arguments for all its parameters also defines the default constructor. ↩