我正在尝试理解C ++的某些方面。
我编写了这个简短的程序来展示从C ++函数返回对象的不同方法:
#include <iostream>
using namespace std;
// A simple class with only one private member.
class Car{
private:
int maxSpeed;
public:
Car( int );
void print();
Car& operator= (const Car &);
Car(const Car &);
};
// Constructor
Car::Car( int maxSpeed ){
this -> maxSpeed = maxSpeed;
cout << "Constructor: New Car (speed="<<maxSpeed<<") at " << this << endl;
}
// Assignment operator
Car& Car::operator= (const Car &anotherCar){
cout << "Assignment operator: copying " << &anotherCar << " into " << this << endl;
this -> maxSpeed = anotherCar.maxSpeed;
return *this;
}
// Copy constructor
Car::Car(const Car &anotherCar ) {
cout << "Copy constructor: copying " << &anotherCar << " into " << this << endl;
this->maxSpeed = anotherCar.maxSpeed;
}
// Print the car.
void Car::print(){
cout << "Print: Car (speed=" << maxSpeed << ") at " << this << endl;
}
// return automatic object (copy object on return) (STACK)
Car makeNewCarCopy(){
Car c(120);
return c; // object copied and destroyed here
}
// return reference to object (STACK)
Car& makeNewCarRef(){
Car c(60);
return c; // c destroyed here, UNSAFE!
// compiler will say: warning: reference to local variable ‘c’ returned
}
// return pointer to object (HEAP)
Car* makeNewCarPointer(){
Car * pt = new Car(30);
return pt; // object in the heap, remember to delete it later on!
}
int main(){
Car a(1),c(2);
Car *b = new Car(a);
a.print();
a = c;
a.print();
Car copyC = makeNewCarCopy(); // safe, but requires copy
copyC.print();
Car &refC = makeNewCarRef(); // UNSAFE
refC.print();
Car *ptC = makeNewCarPointer(); // safe
if (ptC!=NULL){
ptC -> print();
delete ptC;
} else {
// NULL pointer
}
}
代码似乎没有崩溃,我得到以下输出:
Constructor: New Car (speed=1) at 0x7fff51be7a38
Constructor: New Car (speed=2) at 0x7fff51be7a30
Copy constructor: copying 0x7fff51be7a38 into 0x7ff60b4000e0
Print: Car (speed=1) at 0x7fff51be7a38
Assignment operator: copying 0x7fff51be7a30 into 0x7fff51be7a38
Print: Car (speed=2) at 0x7fff51be7a38
Constructor: New Car (speed=120) at 0x7fff51be7a20
Print: Car (speed=120) at 0x7fff51be7a20
Constructor: New Car (speed=60) at 0x7fff51be79c8
Print: Car (speed=60) at 0x7fff51be79c8
Constructor: New Car (speed=30) at 0x7ff60b403a60
Print: Car (speed=30) at 0x7ff60b403a60
现在,我有以下问题:
makeNewCarPointer
作为从C ++函数/方法返回对象的最常用方法。我是对的吗?答案 0 :(得分:5)
makeNewCarCopy安全吗?是否复制和销毁本地对象 在功能的最后?如果是这样,为什么不调用重载 赋值运算符?它是否调用默认的复制构造函数?
这里的重要的问题是“makeNewCarCopy安全吗?”这个问题的答案是,“是的。”您正在制作对象的副本并按值返回该副本。您不会尝试返回对本地自动对象的引用,这是新手之间常见的陷阱,这很好。
这个问题的其他部分的答案在语言上不太重要,虽然一旦你知道如何安全地做到这一点,它们可能在生产代码中变得至关重要。您可能会或可能不会看到本地对象的构造和破坏。实际上,您可能不会,尤其是在启用优化的情况下进行编译时。原因是编译器知道你正在创建一个临时的并返回它,而后者又被复制到其他地方。从某种意义上说,临时变得毫无意义,因此编译器会跳过整个令人烦恼的create-copy-destroy步骤,并简单地在最终意图的变量中直接构造新的副本。这称为copy elision。只要可观察的行为与未进行任何更改(即:As-If Rule)相同,即使在复制构造函数具有副作用的情况下,也允许编译器对程序进行任何和所有更改(请参阅: Return Value Optimization)。
我的胆量告诉我使用makeNewCarPointer作为最常用的方法 从C ++函数/方法返回对象。我是对的吗?
没有。考虑复制省略,正如我上面所描述的那样。所有当代的主要编译器都实现了这种优化,并且做得非常好。因此,如果您可以像复制副指针那样有效地(至少)复制按值,那么复制按指针的性能是否有任何好处?
答案是否定的。这些天,除非你迫切需要,否则你通常希望返回按值。在那些引人注目的需求中,当你需要返回的对象比创建它的“范围”更长时 - 但不就是性能。实际上,动态分配在时间上可能比自动(即“堆栈”)分配更加昂贵。
答案 1 :(得分:2)
是的,makeNewCarCopy
是安全的。理论上会在函数退出时生成副本,但是由于return value optimization允许编译器删除副本。
实际上,这意味着makeNewCarCopy
将有一个隐藏的第一个参数,它是对未初始化Car
的引用,makeNewCarCopy
中的构造函数调用实际上会初始化Car
驻留在函数堆栈框架之外的实例。
关于你的第二个问题:返回一个必须被释放的指针不是首选的方法。这是不安全的,因为分配Car
实例的函数的实现细节被泄露出来,并且调用者负担清理它的负担。如果您需要动态分配,我建议您返回std::shared_ptr<Car>
。
答案 2 :(得分:1)
makeNewCarCopy
是安全的。在大多数情况下,它是有效的,因为编译器可以执行某些优化,如copy elision(并且您没有看到赋值运算符或复制ctor被调用的原因)和/或由C ++ 11添加的move semantics < / LI>
makeNewCarPointer
可能非常有效,但同时又非常危险。问题是您可以轻松忽略返回值,编译器不会产生任何警告。所以至少你应该返回像std::unique_ptr
或std::shared_ptr
这样的智能指针。但恕我直言以前的方法是更优选的,至少不会慢。如果你因为不同的原因必须在堆上创建对象,则会有不同的故事。