简体   繁体   English

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

[英]Returning automatic local objects in C++

I'm trying to understand some aspects of C++. 我正在尝试理解C ++的某些方面。

I have written this short programme to show different ways of returning objects from functions in 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
    }
}

The code doesn't seem to crash, and I get the following output: 代码似乎没有崩溃,我得到以下输出:

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

Now, I have the following questions: 现在,我有以下问题:

  • Is makeNewCarCopy safe? makeNewCarCopy安全吗? Is the local object being copied and destroyed at the end of the function? 是否在函数末尾复制和销毁本地对象? If so, why isn't it calling the overloaded assignment operator? 如果是这样,为什么不调用重载赋值运算符? Does it call the default copy constructor? 它是否调用默认的复制构造函数?
  • My guts tell me to use makeNewCarPointer as the most usual way of returning objects from a C++ function/method. 我的胆量告诉我使用makeNewCarPointer作为从C ++函数/方法返回对象的最常用方法。 Am I right? 我对吗?

Is makeNewCarCopy safe? makeNewCarCopy安全吗? Is the local object being copied and destroyed at the end of the function? 是否在函数末尾复制和销毁本地对象? If so, why isn't it calling the overloaded assignment operator? 如果是这样,为什么不调用重载赋值运算符? Does it call the default copy constructor? 它是否调用默认的复制构造函数?

The important question here is "Is makeNewCarCopy safe?" 这里的重要问题是“makeNewCarCopy安全吗?” The answer to that question is, "yes." 这个问题的答案是,“是的。” You are making a copy of the object and returning that copy by-value. 您正在制作对象的副本并按值返回该副本。 You do not attempt to return a reference to a local automatic object, which is a common pitfall among newbies, and that is good. 您不会尝试返回对本地自动对象的引用,这是新手之间常见的陷阱,这很好。

The answers to the other parts of this question are philisophically less important, although once you know how to do this safely they may become critically important in production code. 这个问题的其他部分的答案在语言上不太重要,虽然一旦你知道如何安全地做到这一点,它们可能在生产代码中变得至关重要。 You may or may not see construction and destruction of the local object. 您可能会或可能不会看到本地对象的构造和破坏。 In fact, you probably won't, especially when compiling with optimizations turned on. 实际上,您可能不会,尤其是在启用优化的情况下进行编译时。 The reason is because the compiler knows that you are creating a temporary and returning that, which in turn is being copied somewhere else. 原因是编译器知道你正在创建一个临时的并返回它,而后者又被复制到其他地方。 The temporary becomes meaningless in a sense, so the compiler skips the whole bothersome create-copy-destroy step and simply constructs the new copy directly in the variable where it's ultimately intended. 从某种意义上说,临时变得毫无意义,因此编译器会跳过整个令人烦恼的create-copy-destroy步骤,并直接在最终预期的变量中构造新副本。 This is called copy elision . 这称为复制省略 Compilers are allowed to make any and all changes to your program so long as the observable behavior is the same as if no changes were made (see: As-If Rule ) even in cases where the copy constructor has side-effects (see: Return Value Optimization ) . 只要可观察的行为与没有进行任何更改(参见: As-If规则 )相同,即使在复制构造函数具有副作用的情况下,编译器也可以对程序进行任何和所有更改(请参阅: 返回价值优化 )。

My guts tell me to use makeNewCarPointer as the most usual way of returning objects from a C++ function/method. 我的胆量告诉我使用makeNewCarPointer作为从C ++函数/方法返回对象的最常用方法。 Am I right? 我对吗?

No. Consider copy elision, as I described it above. 不,请考虑复制省略,正如我上面所描述的那样。 All contemporary, major compilers implement this optimization, and do a very good job at it. 所有当代的主要编译器都实现了这种优化,并且做得非常好。 So if you can copy by-value as efficiently (at least) as copy by-pointer, is there any benefit to copy by-pointer with respect to performance? 因此,如果您可以像复制副指针那样有效地(至少)复制按值,那么复制按指针的性能是否有任何好处?

The answer is no. 答案是不。 These days, you generally want to return by-value unless you have compelling need not to . 这些天,除非你有迫切的需要,否则你通常希望返回按值。 Among those compelling needs are when you need the returned object to outlive the "scope" in which it was created -- but not among those is performance. 在那些引人注目的需求中,当您需要返回的对象比创建它的“范围”更长时 - 但其中包括性能。 In fact, dynamic allocation can be significantly more expensive time-wise than automatic (ie, "stack") allocation. 事实上,动态分配在时间上可能比自动(即“堆栈”)分配更加昂贵。

Yes, makeNewCarCopy is safe. 是的, makeNewCarCopy是安全的。 Theoretically there will be a copy made as the function exits, however because of the return value optimization the compiler is allowed to remove the copy. 理论上会在函数退出时生成一个副本,但是由于返回值优化 ,允许编译器删除副本。

In practice this means that makeNewCarCopy will have a hidden first parameter which is a reference to an uninitialized Car and the constructor call inside makeNewCarCopy will actually initialize the Car instance that resides outside of the function's stack frame. 在实践中,这意味着makeNewCarCopy将具有隐藏的第一个参数,该参数是对未初始化的Car的引用,并且makeNewCarCopy内的构造函数调用实际上将初始化驻留在函数的堆栈帧之外的Car实例。

As to your second question: Returning a pointer that has to be freed is not the preferred way. 关于你的第二个问题:返回一个必须被释放的指针不是首选方式。 It's unsafe because the implementation detail of how the function allocated the Car instance is leaked out and the caller is burdened with cleaning it up. 这是不安全的,因为分配Car实例的函数的实现细节被泄露出来,并且调用者负担清理它的负担。 If you need dynamic allocation then I suggest that you return an std::shared_ptr<Car> instead. 如果你需要动态分配,那么我建议你返回一个std::shared_ptr<Car>

  • Yes makeNewCarCopy is safe. 是的makeNewCarCopy是安全的。 And in most cases it is effective as compiler can do certain optimizations like copy elision (and that the reason you do not see assignment operator or copy ctor called) and/or move semantics added by C++11 并且在大多数情况下,它是有效的,因为编译器可以执行某些优化,例如复制省略 (以及您没有看到赋值运算符或复制ctor调用的原因)和/或移动由C ++ 11添加的语义
  • makeNewCarPointer can be very effective, but is is very dangerous at the same time. makeNewCarPointer可以非常有效,但同时又非常危险。 The problem is you can easily ignore return value and compiler will not produce any warnings. 问题是您可以轻松忽略返回值,编译器不会产生任何警告。 So at least you should return smart pointer like std::unique_ptr or std::shared_ptr . 所以至少你应该返回像std::unique_ptrstd::shared_ptr这样的智能指针。 But IMHO previous method is more preferred and would be at least not slower. 但恕我直言以前的方法是更优选的,至少不会慢。 Different story if you have to create object on heap by different reason. 不同的故事,如果你必须由于不同的原因在堆上创建对象。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM