问题描述
嗨,我想了解复制构造函数的工作原理并查看示例。示例如下:
int v[] = { 1,2,3 }; printf("%p\n",v);
我的问题是:
- 为什么写成“复制构造函数将 *p 复制到
{//new scope Sales_data *p = new Sales_data; auto p2 = make_shared<Saled_data>(); Sales_data item(*p); // copy constructor copies *p into item vector<Sales_data> vec; vec.push_back(*p2);// copies the object to which p2 points delete p; }
”?我的意思是,item
是直接初始化的。如果我们写item
那么它会被称为复制初始化,那么为什么他们在注释中写了复制构造函数将 *p 复制到 item 中。
现在,为了自己验证这一点,我尝试自己创建一个简单的示例,但我也无法正确理解该概念。我的自定义示例如下:
Sales_data item = *p;
现在当我运行这个程序时,我得到以下输出:
这是默认初始化
这是直接初始化
这是直接初始化
我从这个程序中提出的问题如下:
- 为什么在第二种情况下,当我写
#include<iostream> #include<string> class MAINCLASS{ private: std::string name; int age =0; public: MAINCLASS(){ std::cout<<"This is default initialization"<<std::endl; } MAINCLASS(MAINCLASS &obj){ std::cout<<"This is direct initialization"<<std::endl; } MAINCLASS(const MAINCLASS &obj):name(obj.name),age(obj.age){ std::cout<<"This is copy initialization"<<std::endl; } }; int main(){ MAINCLASS objectone; MAINCLASS objecttwo =objectone; MAINCLASS objectthree(objectone); return 0; }
时,我们没有得到输出“这是复制初始化”?我已经读到在直接初始化函数匹配中使用,在复制构造函数中,我们将右手操作数成员复制到左手操作数成员中。因此,当我编写MAINCLASS objecttwo =objectone;
时,它应该调用复制构造函数并在屏幕上打印“这是复制初始化”。但它是直接初始化对象。这里发生了什么?
解决方法
不要混淆复制构造和复制初始化。您可以使用直接或复制初始化进行复制构造。
Copy initialisation 指的是一组初始化语法和语义。这包括 T a = b
语法。
copy constructor 是一个特殊的类方法,它接受所述类的参数。这个方法应该只接受一个参数(T&
或 const T&
都可以)。复制构造在调用该函数时发生。
考虑到这一点,我们可以继续回答您的问题。
- 为什么写成“复制构造函数将 *p 复制到
item
”?我的意思是,item
是直接初始化的。如果我们写Sales_data item = *p;
那么它将被称为复制初始化...
Sales_data item = *p
和 Sales_data item(*p)
都调用了复制构造函数。但是,前者使用复制初始化(T a = b
),而后者使用直接初始化(T a(b)
)。
- 为什么在第二种情况下,当我写
MAINCLASS objecttwo =objectone;
时,我们没有得到输出“这是复制初始化”?
实际上,这里的问题不在于它是否被复制/直接初始化。这是左值/右值重载解析的问题。
考虑以下程序:
#include <iostream>
void f(int& i) { std::cout << "int&\n"; }
void f(const int& i) { std::cout << "const int&\n"; }
int main() {
f(1); // f(const int&)
int i = 2;
f(i); // f(int&)
}
f
是根据传递的值是左值还是右值来选择的。在第一种情况下,1
是一个右值,因此调用 f(const int&)
(参见 this)。在第二种情况下,i
是左值,选择 f(int&)
是因为它更通用。
因此,在您的情况下,MAINCLASS objecttwo =objectone;
和 MAINCLASS objectthree(objectone);
都调用了复制构造函数。同样,前者使用复制初始化,而后者使用直接初始化。只是这两个调用都选择了非常量引用重载:MAINCLASS(MAINCLASS&)
。
尽管名称选择不当,复制初始化与复制构造函数是正交的。
复制构造函数是任何构造函数,其第一个参数是对其类类型的左值引用,并且可以只用一个参数调用。它只是一个构造函数,可以从现有对象初始化新对象。这就是它的全部内容。您声明的两个构造函数实际上都是复制构造函数。这个也太
MAINCLASS(MAINCLASS volatile &obj,void *cookie = nullptr) {
// .. Do something
// This is a copy c'tor since this is valid:
// MAINCLASS volatile vo;
// MAINCLASS copy1_vo(vo);
}
正如其他答案所指出的,复制初始化只是一系列初始化上下文的名称。它包括涉及 =
的初始化、将参数传递给函数、返回语句和 throw 表达式(我可能忘记了一些东西)。直接初始化涉及其他上下文。
复制构造函数可用于上述任何一种。无论是复制初始化还是直接初始化。两者之间的区别 - 属于构造函数 - 是如何构建构造函数的重载集。复制初始化不使用显式声明的构造函数。例如,在这个例子中
struct Example {
Example() = default;
explicit Example(Example const&) {}
};
int main() {
Example e;
Example e1(e); // Okay,direct initialization
Example e2 = e1; // Error! Copy initialization doesn't make use of explicit constructor
}
即使我们有一个复制构造函数,也不能在复制初始化上下文中调用它!
至于程序的意外打印,这只是重载决议选择更匹配函数的问题。您的原始对象未声明为 const。因此,将其绑定到非常量左值引用只是重载解析中的首选。
,复制初始化和直接初始化是根据用于构造的语法。
见Confusion in copy initialization and direct initialization。
哪个构造函数被调用是基于重载解析(而不是构造的语法) 编译器调用与传递的参数与定义的参数最匹配的函数。
在您的示例中,由于 objectone
是非常量,最佳匹配是带有非常量参数的复制构造函数。由于另一个复制构造函数有一个 const&
参数,它将为 const 对象调用。
重写您的示例:
#include<iostream>
#include<string>
class MAINCLASS {
private:
std::string name;
int age = 0;
public:
MAINCLASS() {
std::cout << "This is default initialization" << std::endl;
}
MAINCLASS(MAINCLASS& obj) {
std::cout << "This is copy constructor with non-const reference parameter" << std::endl;
}
MAINCLASS(const MAINCLASS& obj) :name(obj.name),age(obj.age) {
std::cout << "This is copy constructor with const reference parameter" << std::endl;
}
};
int main() {
MAINCLASS objectone;
const MAINCLASS const_objectone;
MAINCLASS objecttwo = objectone; // copy initialization of non-const object
MAINCLASS objectthree(objectone); // direct initialization of non-const object
MAINCLASS objectfour = const_objectone; // copy initialization of const object
MAINCLASS objectfive(const_objectone); // direct initialization of const object
return 0;
}
输出将是:
This is default initialization
This is default initialization
This is copy constructor with non-const reference parameter
This is copy constructor with non-const reference parameter
This is copy constructor with const reference parameter
This is copy constructor with const reference parameter