1. C++智能指针中引用计数的实现为什么不用static int count这种形式?
答:C++ 中shared_ptr是一个模板类,其 引用计数使用的是私有的指针 int* count,所有实例通过共同的指针来对 引用计数count实现增减。
之所以没有使用 static int count这种形式,是因为 静态成员变量为所有类实例共享,这在有些情况下会导致引用计数发生错误,比如:
SmartPointer<int> a;
// ...
SmartPointer<int> b;
SmartPointer<int> c = b; // ref count increased due to copying.
当执行完SmartPointer<int> c = b;时, b的引用计数+1,变为2。 但由于count是static的,在所有SmartPointer<int>的实例之间共享,所以 对象a的引用计数也会变为2。
这显然与我们的期望不符。所以,不能使用static形式的引用计数。
2. C++中拷贝构造函数中的参数一定要是引用形式的吗?如果是,原因是什么?
答:拷贝构造函数中的参数一定要是引用形式的。
拷贝构造函数形式如下:
class A
{
public:
A(int num):num_(num){ }
A(const A& A_Ins);
private:
int num_;
};
拷贝构造函数必须以引用的方式传递参数。这是因为,在值传递的方式传递给一个函数的时候,会调用拷贝构造函数生成函数的实参。如果拷贝构造函数的参数仍然是以值的方式,就会无限循环的调用下去,直到函数的栈溢出。
调用拷贝构造函数主要有以下场景:
- 对象作为函数的参数,以值传递的方式传给函数。
- 对象作为函数的返回值,以值的方式从函数返回
- 使用一个对象给另一个对象初始化
3.拷贝构造函数和赋值运算符
class A
{
public:
A(int num):num_:num { }
A(A& aIns); // 拷贝构造函数
A& operator=(const A& aIns); // 赋值运算符
int num_;
};
拷贝构造函数必须以引用的方式传递参数。这是因为,在值传递的方式传递给一个函数的时候,会调用拷贝构造函数生成函数的实参。如果拷贝构造函数的参数仍然是以值的方式,就会无限循环的调用下去,直到函数的栈溢出。
Q:对于一个类实例,调用 operato = 一定是调用其 赋值运算符吗?
A:不一定。有可能是在调用拷贝构造函数。区分到底是调用拷贝构造函数还是赋值运算符重载,要看有没有生成新的实例;有生成新的实例,是在调用拷贝构造函数,否则是在调用复制运算符。
A ins1(999); //
A ins2 = ins1; // 生成了新的实例ins2,所以是调用拷贝构造函数。
A ins3(9988);
ins2 = ins3; // 没有生成新的实例,所以是调用 赋值运算符重载。
3.移动构造函数和移动赋值运算符
移动构造函数类似于拷贝构造函数,不同的是移动构造函数的第一个参数是一个右值引用(&&)。
如果一个类的成员都是基本数据类型或者不含有指向堆上分配的内存的指针,那么移动构造与拷贝构造性能无差异。
如果类里有其它资源:例如 动态分配的内存、指向其他数据的指针等,拷贝构造函数中需要以深拷贝(而非浅拷贝)的方式复制对象的所有数据。而移动构造函数仅仅移动数据成员(把原对象里指针成员的指向地址拿过来),不会分配新的内存,所以比拷贝构造函数性能更好。
与处理拷贝构造函数和拷贝赋值运算符一样,编译器也会合成移动构造函数和移动赋值运算符,如果一个类定义了自己的拷贝构造函数,拷贝赋值运算符或析构函数,编译器就不会为它合成移动构造函数和移动赋值运算符。
当一个类没有定义任何自己版本的拷贝构造函数,拷贝赋值运算符,析构函数,且类的每个非静态数据成员都可以移动时,编译器才会为它合成移动构造函数或移动赋值运算符。
class Person
{
public:
const char* name_;
Person(const char* name):name_(name) {};
Person(const Person& person)
{
name_ = person.name_;
std::cout << "拷贝构造函数" << std::endl;
}
Person(const Person&& person)
{
name_ = person.name_;
std::cout << "移动构造函数"<< std::endl;
}
Person& operator=(const Person& person)
{
name_ = person.name_;
std::cout << "拷贝赋值运算符" << std::endl;
return *this;
}
Person& operator=(Person&& person)
{
if (this != &person) {
name_ = person.name_;
std::cout << "移动赋值运算符" << std::endl;
}
return *this;
}
};
Person getPerson()
{
Person person("person in func");
return person;
}
int main(int argc, char* argv[])
{
Person person1("xiaohong");
Person person2("xiaoming");
Person person3(getPerson());//移动构造函数
Person person4(std::move(person1));//移动构造函数
Person person5(person2);//拷贝构造函数
Person person6("xiaolan");
person6 = std::move(person3);//移动赋值运算
Person person7("xiaoqi");
person7 = Person("xiaoqi");//移动赋值运算
Person person8("xiaoba");
person8 = person1;//拷贝赋值运算符
system("pause");
return 0;
}
Person p1;
std::move(p1) 会把一个左值p1 强制转换为 右值,这样使用p1对其他对象进行赋值或者构造的时候,就可以使用移动赋值/移动构造,提高效率。 可以把move() 理解为 cast_to_right_value()
需要注意的是:从逻辑上来讲,被 std::move()的对象,不应该再继续被使用。