我正在尝试学习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;
(当然,我可能也不理解这一点。)
哪种方式被认为是最佳做法,应该遵循?
答案 0 :(得分:48)
选项1 有缺陷。声明对象时
QList<Weight *> ret;
它只存在于本地范围内。函数退出时会被销毁。但是,您可以使用
进行此操作return ret; // no "&"
现在,虽然ret
被销毁,但首先会复制一份副本并传回给调用者。
这是一般首选的方法。事实上,复制和销毁操作(实际上什么也没有完成)通常是elided, or optimized out,你可以得到一个快速,优雅的程序。
选项2 有效,但是你有一个指向堆的指针。查看C ++的一种方法是该语言的目的是避免手动内存管理等。有时您确实希望管理堆上的对象,但选项1仍然允许:
QList<Weight *> *myList = new QList<Weight *>( getWeights() );
其中getWeights
是您的示例函数。 (在这种情况下,您可能必须定义复制构造函数QList::QList( QList const & )
,但与前面的示例一样,它可能不会被调用。)
同样,你可能应该避免使用指针列表。该列表应直接存储对象。尝试使用std::list
...使用语言功能练习比实践数据结构更重要。
答案 1 :(得分:7)
使用选项#1略有变化;而不是返回对本地创建的对象的引用,返回其副本。
即。 return ret;
大多数C ++编译器执行Return value optimization (RVO)来优化创建的临时对象以保存函数的返回值。
答案 2 :(得分:5)
通常,您不应该返回引用或指针。而是返回对象的副本或返回拥有该对象的智能指针类。通常,使用静态存储分配,除非在运行时大小不同或对象的生命周期要求使用动态存储分配进行分配。
正如已经指出的那样,通过引用返回的示例返回对不再存在的对象的引用(因为它已超出范围),因此调用未定义的行为。这是你永远不应该返回引用的原因。你永远不应该返回原始指针,因为所有权不明确。
还应该注意的是,由于返回值优化(RVO),按值返回非常便宜,并且由于引入了右值参考,很快就会更便宜。
答案 3 :(得分:1)
BUT IT VERY-MUCH DEPENDS ON WHAT EXACTLY YOU WANT TO DO
)
在你的选项1中,你返回地址并且非常糟糕,因为这可能导致未定义的行为。 (ret将被释放,但是你将在被调用的函数中访问ret的地址)
所以请使用return ret;
答案 4 :(得分:1)
分配必须在别处释放的内存通常是不好的做法。这是我们拥有C ++而不仅仅是C的原因之一。(但精明的程序员在Stroustrup时代之前很久就在C语言中编写面向对象的代码。)构造良好的对象具有快速复制和赋值运算符(有时使用引用计数) ,当它们被释放并自动调用它们的DTOR时,它们会自动释放它们“拥有”的内存。所以你可以愉快地抛弃它们,而不是使用它们的指针。
因此,根据您想要做的事情,最佳做法很可能是“以上都不是”。每当你想要在CTOR之外的任何地方使用“新”时,请考虑一下。可能你根本不想使用“新”。如果这样做,结果指针应该包含在某种智能指针中。您可以在没有调用“new”的情况下使用数周和数月,因为“new”和“delete”在标准类或类模板(如std :: list和std :: vector)中处理。
一个例外是当您使用像OpenCV这样的旧时尚库时,有时需要您创建一个新对象,并将指向它的指针交给系统,该系统需要所有权。
如果在他们的DTORS中正确地写了QList和Weight以进行清理,你想要的是,
QList<Weight> ret();
Weight weight(cname, "Weight");
ret.append(weight);
ret.append(c);
return ret;
答案 5 :(得分:1)
如前所述,最好避免分配必须在别处解除分配的内存。这是我喜欢做的事情(......现在):
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);
如果您想要返回状态标记,则可以随时使用bool
或enum
。
答案 6 :(得分:1)
根据经验,不要使用普通指针,因为你很容易忘记添加适当的破坏机制。
如果您想避免复制,可以使用复制构造函数和禁用复制操作符来实现权重类:
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_ptr
或unique_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你也可以采取&#39;来自集合的对象,制作副本,从集合中删除但在本地使用 - 在幕后它只是同一个对象。
答案 7 :(得分:-3)
所有这些都是有效的答案,避免指针,使用复制构造函数等。除非您需要创建一个需要良好性能的程序,根据我的经验,大多数与性能相关的问题都是复制构造函数,并且导致开销被他们。 (智能指针在这个领域没有任何好处,我会删除所有的提升代码并进行手动删除,因为它需要花费太多毫秒来完成它的工作。)
如果你正在创建一个“简单”程序(虽然“简单”意味着你应该使用java或C#)然后使用复制构造函数,避免指针并使用智能指针来释放已用内存,如果你正在创建一个复杂的程序或者你需要一个好的性能,在整个地方使用指针,并避免复制构造函数(如果可能的话),只需创建一组规则来删除指针并使用valgrind来检测内存泄漏,
也许我会得到一些负面观点,但我认为你需要全面了解你的设计选择。
我认为,如果你指的是你的设计是错误的,那么这就是一个误导。输出参数往往令人困惑,因为它不是“返回”结果的自然选择。
我知道这个问题已经过时了,但是我没有看到任何其他论点指出这些设计选择的性能开销。