在C++中返回自动本地对象

Mul*_*one 4 c++ return copy-constructor

我正在尝试理解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
    }
}
Run Code Online (Sandbox Code Playgroud)

代码似乎没有崩溃,我得到以下输出:

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
Run Code Online (Sandbox Code Playgroud)

现在,我有以下问题:

  • makeNewCarCopy安全吗?是否在函数末尾复制和销毁本地对象?如果是这样,为什么不调用重载赋值运算符?它是否调用默认的复制构造函数?
  • 我的胆量告诉我使用makeNewCarPointerC++函数/方法返回对象的最常用方法.我对吗?

Joh*_*ing 5

makeNewCarCopy安全吗?是否在函数末尾复制和销毁本地对象?如果是这样,为什么不调用重载赋值运算符?它是否调用默认的复制构造函数?

这里的重要问题是"makeNewCarCopy安全吗?" 这个问题的答案是,"是的." 您正在制作对象的副本并按值返回该副本.您不会尝试返回对本地自动对象的引用,这是新手之间常见的陷阱,这很好.

这个问题的其他部分的答案在语言上不太重要,虽然一旦你知道如何安全地做到这一点,它们可能在生产代码中变得至关重要.您可能会或可能不会看到本地对象的构造和破坏.实际上,您可能不会,尤其是在启用优化的情况下进行编译时.原因是编译器知道你正在创建一个临时的并返回它,而后者又被复制到其他地方.从某种意义上说,临时变得毫无意义,因此编译器会跳过整个令人烦恼的create-copy-destroy步骤,并直接在最终预期的变量中构造新副本.这称为复制省略.只要可观察的行为与没有进行任何更改(参见:As-If规则)相同,即使在复制构造函数具有副作用的情况下,编译器也可以对程序进行任何和所有更改(请参阅:返回价值优化).

我的胆量告诉我使用makeNewCarPointer作为从C++函数/方法返回对象的最常用方法.我对吗?

不,请考虑复制省略,正如我上面所描述的那样.所有当代的主要编译器都实现了这种优化,并且做得非常好.因此,如果您可以像复制副指针一样有效地(至少)复制按值,那么复制按指针的性能是否有任何好处?

答案是不.这些天,除非你有迫切的需要,否则你通常希望返回按值.在那些引人注目的需求中,当您需要返回的对象比创建它的"范围"更长时 - 但其中包括性能.事实上,动态分配在时间上可能比自动(即"堆栈")分配更加昂贵.