繁体   English   中英

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

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

我正在尝试学习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 ++的一种方法是该语言的目的是避免手动内存管理等。 有时您确实希望管理堆上的对象,但选项1仍然允许:

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

其中getWeights是您的示例函数。 (在这种情况下,您可能必须定义一个复制构造函数QList::QList( QList const & ) ,但是像前面的示例一样,它可能不会被调用。)

同样,您可能应该避免使用指针列表。 该列表应直接存储对象。 尝试使用std::list ...使用语言功能实践比实践数据结构更重要。

使用选项#1略有变化; 而不是返回对本地创建的对象的引用,返回其副本。

return ret;

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

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

正如已经指出的那样,通过引用返回的示例返回对不再存在的对象的引用(因为它已超出范围),因此调用未定义的行为。 这是你永远不应该返回引用的原因。 您永远不应该返回原始指针,因为所有权不明确。

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

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

如果要避免复制,可以使用复制构造函数和禁用复制操作符来实现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你也可以从集合中“获取”对象,制作副本,从集合中删除但在本地使用 - 在幕后它只是同一个对象。

传递和返回引用邀请责任。 你需要注意,当你修改一些值时,没有副作用。 在指针的情况下也一样。 我建议你重新调整物体。 BUT IT VERY-MUCH DEPENDS ON WHAT EXACTLY YOU WANT TO DO

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

所以使用return ret;

分配必须在其他地方释放的内存通常是不好的做法。 这是我们拥有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;

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

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

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

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

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

我认为这句话“如果你指的是你的设计错了”,那就不会产生误导了。 输出参数往往令人困惑,因为它不是“返回”结果的自然选择。

我知道这个问题很老,但我没有看到任何其他论点指出设计选择的性能开销。

暂无
暂无

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

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