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

我试图学习C ++,并试图理解返回的对象。 我似乎看到了这样做的两种方式,需要了解什么是最佳实践。

选项1:

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

选项2:

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

(当然,我也可能不了解这一点)。

哪种方式被认为是最佳实践,应该遵循?

选项1有缺陷。 当你声明一个对象

 QList<Weight *> ret; 

它只生活在本地范围内。 当函数退出时它被销毁。 但是,你可以使这个工作

 return ret; // no "&" 

现在,虽然ret被销毁,但是首先复制并传回给调用者。

这是一般首选的方法。 事实上,复制和销毁操作(实际上什么都没有完成)通常被省略,或者被优化 ,你会得到一个快速,优雅的程序。

选项2的作品,但是你有一个指向堆的指针。 看C ++的一种方法是,这种语言的目的是避免像这样的手动内存pipe理。 有时你想要pipe理堆上的对象,但是选项1仍然允许:

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

getWeights是您的示例函数。 (在这种情况下,你可能不得不定义一个拷贝构造函数QList::QList( QList const & ) ,但是和前面的例子一样,它可能不会被调用。

同样,你可能应该避免有一个指针列表。 该列表应直接存储对象。 尝试使用std::list …练习与语言function比实践数据结构更重要。

使用选项#1稍作改动; 而不是返回到本地创build的对象的引用,返回其副本。

return ret;

大多数C ++编译器执行返回值优化(Return value optimization,RVO)来优化创build的临时对象以保存函数的返回值。

一般来说,你永远不应该返回一个引用或指针。 而是返回一个对象的副本或返回一个拥有该对象的智能指针类。 通常,使用静态存储分配,除非在运行时大小不同,或者对象的生命周期要求使用dynamic存储分配进行分配。

正如已经指出的那样,通过引用返回的示例返回对不再存在的对象的引用(因为它已经超出了作用域),因此调用了未定义的行为。 这是你永远不应该返回一个参考的原因。 你永远不应该返回一个原始指针,因为所有权不清楚。

还应该注意的是,由于返回值优化(RVO),按值返回是非常便宜的,并且由于引用了右值引用,即将更便宜。

传递和返回引用邀请负责。 你需要小心,当你修改一些值没有副作用。 同样在指针的情况下。 我build议你重新调整对象。 ( BUT IT VERY-MUCH DEPENDS ON WHAT EXACTLY YOU WANT TO DO

在你的选项1,你返回的地址,这是非常糟糕,因为这可能导致未定义的行为。 (ret将被释放,但是你会在被调用函数中访问ret的地址)

所以使用return ret;

分配必须在其他地方释放的内存通常是不好的做法。 这是我们使用C ++而不仅仅是C的原因之一。(但精明的程序员早在Age of Stroustrup之前就用C编写了面向对象的代码。)构造良好的对象具有快速的复制和赋值操作符(有时使用引用计数) ,当它们被释放并且DTOR自动被调用时,它们自动释放它们“拥有”的内存。 所以你可以高兴地把它们折腾起来,而不是用指针指向它们。

因此,取决于你想做什么,最好的做法很可能是“以上都不是”。 每当你想在除了CTOR以外的任何地方使用“新”,想想看。 可能你根本不想用“新”。 如果这样做,结果指针应该可能被包装在某种智能指针中。 你可以花上几个星期和几个月的时间,而不需要调用“新”,因为“新”和“删除”是在标准类或类模板,如std :: list和std :: vector中处理的。

一个例外是,当你使用像OpenCV这样的旧时尚库时,有时需要你创build一个新的对象,然后把指向它的指针交给拥有所有权的系统。

如果QList和Weight被正确地写在自己的DTORS后面清理,你想要的是,

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

如前所述,最好避免分配必须在别处解除分配的内存。 这是我喜欢做的(…这些天):

 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) 

更好 – 完全避免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); 

如果你想返回一个状态标志,你总是可以使用boolenum

根据经验,不要使用普通的指针,因为你可以很容易忘记添加适当的销毁机制。

如果你想避免复制,你可以用复制构造函数和复制操作符来实现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&); }; 

然后,对于实现容器的类,使用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)); } }; 

您添加元素的函数将使用或返回值优化,并且不会调用复制构造函数(未定义):

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

在添加一个元素时,让容器取得对象的所有权,所以不要释放它:

 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); 

或者,更好的是,强制用户只传递你的QList实现智能指针:

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

在QList之外,你会使用它:

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

使用shared_ptr你也可以从集合中获取对象,制作副本,从集合中删除,但在本地使用 – 在幕后它将只是同一个唯一的对象。

所有这些都是有效的答案,避免指针,使用拷贝构造函数等。除非需要创build一个需要良好性能的程序,根据我的经验,大多数与性能相关的问题都与拷贝构造函数有关,以及由它们造成的开销。 (智能指针在这个领域没有更好的,我会删除所有的升压代码,并进行手动删除,因为它需要太多的毫秒来完成它的工作)。

如果你正在创build一个“简单”的程序(虽然“简单”意味着你应该去与Java或C#),然后使用复制构造函数,避免指针和使用智能指针释放使用的内存,如果你正在创build一个复杂的程序或你需要一个良好的性能,使用指针到处都是,并避免复制构造函数(如果可能的话),只需创build一套规则来删除指针,并使用valgrind来检测内存泄漏,

也许我会得到一些负面的观点,但我认为你需要全面了解你的deviseselect。

我认为这样说“如果你返回指针,你的devise是错误的”是没有什么误导。 输出参数往往会令人困惑,因为它不是“返回”结果的自然select。

我知道这个问题是旧的,但我没有看到任何其他的论点指出了deviseselect的性能开销。