简体   繁体   English

C ++最佳实践:返回引用与对象

[英]C++ best practice: Returning reference vs. object

I'm trying to learn C++, and trying to understand returning objects. 我正在尝试学习C ++,并试图理解返回的对象。 I seem to see 2 ways of doing this, and need to understand what is the best practice. 我似乎看到了两种方法,并且需要了解什么是最佳实践。

Option 1: 选项1:

QList<Weight *> ret;
Weight *weight = new Weight(cname, "Weight");
ret.append(weight);
ret.append(c);
return &ret;

Option 2: 选项2:

QList<Weight *> *ret = new QList();
Weight *weight = new Weight(cname, "Weight");
ret->append(weight);
ret->append(c);
return ret;

(of course, I may not understand this yet either). (当然,我也可能不理解这一点)。

Which way is considered best-practice, and should be followed? 哪种方式被认为是最佳实践,应该遵循?

Option 1 is defective. 选项1有缺陷。 When you declare an object 声明对象时

QList<Weight *> ret;

it only lives in the local scope. 它只存在于当地范围内。 It is destroyed when the function exits. 函数退出时会被销毁。 However, you can make this work with 但是,您可以使用它

return ret; // no "&"

Now, although ret is destroyed, a copy is made first and passed back to the caller. 现在,虽然ret被销毁,但是首先复制并传回给调用者。

This is the generally preferred methodology. 这是通常优选的方法。 In fact, the copy-and-destroy operation (which accomplishes nothing, really) is usually elided, or optimized out and you get a fast, elegant program. 实际上,复制和销毁操作(实际上没有完成任何操作)通常被省略或优化,并且您可以获得快速,优雅的程序。

Option 2 works, but then you have a pointer to the heap. 选项2有效,但是你有一个指向堆的指针。 One way of looking at C++ is that the purpose of the language is to avoid manual memory management such as that. 查看C ++的一种方法是该语言的目的是避免手动内存管理等。 Sometimes you do want to manage objects on the heap, but option 1 still allows that: 有时您确实希望管理堆上的对象,但选项1仍然允许:

QList<Weight *> *myList = new QList<Weight *>( getWeights() );

where getWeights is your example function. 其中getWeights是您的示例函数。 (In this case, you may have to define a copy constructor QList::QList( QList const & ) , but like the previous example, it will probably not get called.) (在这种情况下,您可能必须定义一个复制构造函数QList::QList( QList const & ) ,但是像前面的示例一样,它可能不会被调用。)

Likewise, you probably should avoid having a list of pointers. 同样,您可能应该避免使用指针列表。 The list should store the objects directly. 该列表应直接存储对象。 Try using std::list … practice with the language features is more important than practice implementing data structures. 尝试使用std::list ...使用语言功能实践比实践数据结构更重要。

Use the option #1 with a slight change; 使用选项#1略有变化; instead of returning a reference to the locally created object, return its copy. 而不是返回对本地创建的对象的引用,返回其副本。

ie return ret; return ret;

Most C++ compilers perform Return value optimization (RVO) to optimize away the temporary object created to hold a function's return value. 大多数C ++编译器执行返回值优化(RVO)以优化创建的临时对象以保存函数的返回值。

In general, you should never return a reference or a pointer. 通常,您不应该返回引用或指针。 Instead, return a copy of the object or return a smart pointer class which owns the object. 而是返回对象的副本或返回拥有该对象的智能指针类。 In general, use static storage allocation unless the size varies at runtime or the lifetime of the object requires that it be allocated using dynamic storage allocation. 通常,使用静态存储分配,除非在运行时大小不同或对象的生命周期要求使用动态存储分配进行分配。

As has been pointed out, your example of returning by reference returns a reference to an object that no longer exists (since it has gone out of scope) and hence are invoking undefined behavior. 正如已经指出的那样,通过引用返回的示例返回对不再存在的对象的引用(因为它已超出范围),因此调用未定义的行为。 This is the reason you should never return a reference. 这是你永远不应该返回引用的原因。 You should never return a raw pointer, because ownership is unclear. 您永远不应该返回原始指针,因为所有权不明确。

It should also be noted that returning by value is incredibly cheap due to return-value optimization (RVO), and will soon be even cheaper due to the introduction of rvalue references. 还应该注意的是,由于返回值优化(RVO),按值返回非常便宜,并且由于引入了右值引用而很快会更便宜。

Based on experience, do not use plain pointers because you can easily forget to add proper destruction mechanisms. 根据经验,不要使用普通指针,因为您很容易忘记添加适当的销毁机制。

If you want to avoid copying, you can go for implementing the Weight class with copy constructor and copy operator disabled: 如果要避免复制,可以使用复制构造函数和禁用复制操作符来实现Weight类:

class Weight { 
protected:
    std::string name;
    std::string desc;
public:
    Weight (std::string n, std::string d) 
        : name(n), desc(d) {
        std::cout << "W c-tor\n"; 
    }
    ~Weight (void) {
        std::cout << "W d-tor\n"; 
    }

    // disable them to prevent copying
    // and generate error when compiling
    Weight(const Weight&);
    void operator=(const Weight&);
};

Then, for the class implementing the container, use shared_ptr or unique_ptr to implement the data member: 然后,对于实现容器的类,使用shared_ptrunique_ptr来实现数据成员:

template <typename T>
class QList {
protected:
    std::vector<std::shared_ptr<T>> v;
public:
    QList (void) { 
        std::cout << "Q c-tor\n"; 
    }
    ~QList (void) { 
        std::cout << "Q d-tor\n"; 
    }

    // disable them to prevent copying
    QList(const QList&);
    void operator=(const QList&);

    void append(T& t) {
        v.push_back(std::shared_ptr<T>(&t));
    }
};

Your function for adding an element would make use or Return Value Optimization and would not call the copy constructor (which is not defined): 您添加元素的函数将使用或返回值优化,并且不会调用复制构造函数(未定义):

QList<Weight> create (void) {
    QList<Weight> ret;
    Weight& weight = *(new Weight("cname", "Weight"));
    ret.append(weight);
    return ret;
}

On adding an element, the let the container take the ownership of the object, so do not deallocate it: 在添加元素时,让容器获取对象的所有权,因此不要释放它:

QList<Weight> ql = create();
ql.append(*(new Weight("aname", "Height")));

// this generates segmentation fault because
// the object would be deallocated twice
Weight w("aname", "Height");
ql.append(w);

Or, better, force the user to pass your QList implementation only smart pointers: 或者,更好的是,强制用户只传递你的QList实现智能指针:

void append(std::shared_ptr<T> t) {
    v.push_back(t);
}

And outside class QList you'll use it like: 在课堂QList之外,您将使用它:

Weight * pw = new Weight("aname", "Height");
ql.append(std::shared_ptr<Weight>(pw));

Using shared_ptr you could also 'take' objects from collection, make copies, remove from collection but use locally - behind the scenes it would be only the same only object. 使用shared_ptr你也可以从集合中“获取”对象,制作副本,从集合中删除但在本地使用 - 在幕后它只是同一个对象。

passing & returning references invites responsibilty.! 传递和返回引用邀请责任。 u need to take care that when you modify some values there are no side effects. 你需要注意,当你修改一些值时,没有副作用。 same in the case of pointers. 在指针的情况下也一样。 I reccomend you to retun objects. 我建议你重新调整物体。 ( BUT IT VERY-MUCH DEPENDS ON WHAT EXACTLY YOU WANT TO DO ) BUT IT VERY-MUCH DEPENDS ON WHAT EXACTLY YOU WANT TO DO

In ur Option 1, you return the address and Thats VERY bad as this could lead to undefined behaviour. 在您的选项1中,您返回地址并且非常糟糕,因为这可能导致未定义的行为。 (ret will be deallocated, but y'll access ret's address in the called function) (ret将被释放,但是你将在被调用的函数中访问ret的地址)

so use return ret; 所以使用return ret;

It's generally bad practice to allocate memory that has to be freed elsewhere. 分配必须在其他地方释放的内存通常是不好的做法。 That's one of the reasons we have C++ rather than just C. (But savvy programmers were writing object-oriented code in C long before the Age of Stroustrup.) Well-constructed objects have quick copy and assignment operators (sometimes using reference-counting), and they automatically free up the memory that they "own" when they are freed and their DTOR automatically is called. 这是我们拥有C ++而不仅仅是C的原因之一。(但精明的程序员在Stroustrup时代之前很久就在C中编写面向对象的代码。)构造良好的对象具有快速复制和赋值运算符(有时使用引用计数) ,当它们被释放并自动调用它们的DTOR时,它们会自动释放它们“拥有”的内存。 So you can toss them around cheerfully, rather than using pointers to them. 所以你可以愉快地抛弃它们,而不是使用指针。

Therefore, depending on what you want to do, the best practice is very likely "none of the above." 因此,根据您的要求,最佳做法很可能是“以上都不是”。 Whenever you are tempted to use "new" anywhere other than in a CTOR, think about it. 每当你想要在CTOR之外的任何地方使用“新”时,请考虑一下。 Probably you don't want to use "new" at all. 可能你根本不想使用“新”。 If you do, the resulting pointer should probably be wrapped in some kind of smart pointer. 如果这样做,结果指针应该包含在某种智能指针中。 You can go for weeks and months without ever calling "new", because the "new" and "delete" are taken care of in standard classes or class templates like std::list and std::vector. 您可以在没有调用“new”的情况下使用数周和数月,因为“new”和“delete”在标准类或类模板(如std :: list和std :: vector)中得到了处理。

One exception is when you are using an old fashion library like OpenCV that sometimes requires that you create a new object, and hand off a pointer to it to the system, which takes ownership. 一个例外是当您使用像OpenCV这样的旧时尚库时,有时需要您创建一个新对象,并将指向它的指针交给系统,该系统需要所有权。

If QList and Weight are properly written to clean up after themselves in their DTORS, what you want is, 如果在他们的DTORS中正确编写了QList和Weight以进行清理,你想要的是,

QList<Weight> ret();
Weight weight(cname, "Weight");
ret.append(weight);
ret.append(c);
return ret;

As already mentioned, it's better to avoid allocating memory which must be deallocated elsewhere. 如前所述,最好避免分配必须在别处解除分配的内存。 This is what I prefer doing (...these days): 这是我喜欢做的事情(......现在):

void someFunc(QList<Weight *>& list){
    // ... other code
    Weight *weight = new Weight(cname, "Weight");
    list.append(weight);
    list.append(c);
}

// ... later ...

QList<Weight *> list;
someFunc(list)

Even better -- avoid new completely and using std::vector : 更好 - 完全避免使用new和使用std::vector

void someFunc(std::vector<Weight>& list){
    // ... other code
    Weight weight(cname, "Weight");
    list.push_back(weight);
    list.push_back(c);
}

// ... later ...

std::vector<Weight> list;
someFunc(list);

You can always use a bool or enum if you want to return a status flag. 如果要返回状态标志,可以始终使用boolenum

All of these are valid answers, avoid Pointers, use copy constructors, etc. Unless you need to create a program that needs good performance, in my experience most of the performance related problems are with the copy constructors, and the overhead caused by them. 所有这些都是有效的答案,避免指针,使用复制构造函数等。除非你需要创建一个需要良好性能的程序,根据我的经验,大多数与性能相关的问题都是复制构造函数,以及由它们引起的开销。 (And smart pointers are not any better on this field, I'd to remove all my boost code and do the manual delete because it was taking too much milliseconds to do its job). (智能指针在这个领域没有任何好处,我会删除所有的提升代码并进行手动删除,因为它花了太多毫秒来完成它的工作)。

If you're creating a "simple" program (although "simple" means you should go with java or C#) then use copy constructors, avoid pointers and use smart pointers to deallocate the used memory, if you're creating a complex programs or you need a good performance, use pointers all over the place, and avoid copy constructors (if possible), just create your set of rules to delete pointers and use valgrind to detect memory leaks, 如果你正在创建一个“简单”程序(虽然“简单”意味着你应该使用java或C#)然后使用复制构造函数,避免指针并使用智能指针来释放已用内存,如果你正在创建一个复杂的程序或你需要一个好的性能,在各处使用指针,并避免复制构造函数(如果可能的话),只需创建一组规则来删除指针并使用valgrind来检测内存泄漏,

Maybe I will get some negative points, but I think you'll need to get the full picture to take your design choices. 也许我会得到一些负面观点,但我认为你需要全面了解你的设计选择。

I think that saying "if you're returning pointers your design is wrong" is little misleading. 我认为这句话“如果你指的是你的设计错了”,那就不会产生误导了。 The output parameters tends to be confusing because it's not a natural choice for "returning" results. 输出参数往往令人困惑,因为它不是“返回”结果的自然选择。

I know this question is old, but I don't see any other argument pointing out the performance overhead of that design choices. 我知道这个问题很老,但我没有看到任何其他论点指出设计选择的性能开销。

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

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