代码之家  ›  专栏  ›  技术社区  ›  Mike Crowe

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

c++
  •  41
  • Mike Crowe  · 技术社区  · 15 年前

    我正在努力学习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;
    

    (当然,我也可能还不明白这一点)。

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

    8 回复  |  直到 11 年前
        1
  •  47
  •   Potatoswatter    15 年前

    选项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 _具有语言特性的实践比实现数据结构的实践更重要。

        2
  •  6
  •   missingfaktor Kevin Wright    15 年前

    使用选项1,稍作更改;不要返回对本地创建对象的引用,而是返回其副本。

    return ret;

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

        3
  •  5
  •   Michael Aaron Safyan    15 年前

    一般来说,不应该返回引用或指针。相反,返回对象的副本或返回拥有该对象的智能指针类。通常,使用静态存储分配,除非在运行时大小发生变化,或者对象的生存期要求使用动态存储分配来分配。

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

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

        4
  •  1
  •   raj    15 年前

    传递和返回样板客户邀请负责任。!你需要注意的是,当你修改一些值时,没有副作用。指针也一样。我建议你重新放东西。( BUT IT VERY-MUCH DEPENDS ON WHAT EXACTLY YOU WANT TO DO )

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

    所以用 return ret;

        5
  •  1
  •   Jive Dadson hmishra2250    15 年前

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

    因此,根据您想要做什么,最佳实践很可能是“以上都不是”。每当您试图在除ctor之外的任何地方使用“new”时,请考虑一下。也许你根本不想用“新”这个词。如果这样做,结果指针可能应该包装在某种智能指针中。您可以连续数周或数月不调用“new”,因为“new”和“delete”在标准类或类模板(如std::list和std::vector)中得到处理。

    一个例外是,当您使用一个旧的时尚库(如opencv)时,有时需要创建一个新的对象,并将指向它的指针交给系统,这将获得所有权。

    如果Qlist和weight被正确地写在DTORS中以在它们自己之后进行清理,那么您需要的是,

    QList<Weight> ret();
    Weight weight(cname, "Weight");
    ret.append(weight);
    ret.append(c);
    return ret;
    
        6
  •  1
  •   Agnel Kurian    12 年前

    如前所述,最好避免分配必须在其他地方释放的内存。这就是我喜欢做的事(…这些天):

    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 如果要返回状态标志。

        7
  •  1
  •   user3086814    11 年前

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

    如果要避免复制,可以执行 重量 已禁用复制构造函数和复制运算符的类:

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

    在课堂外,你会像这样使用它:

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

    使用共享的指针,您还可以从集合中“获取”对象,制作副本,从集合中删除,但在本地使用-在幕后,它将只是同一个唯一的对象。

        8
  •  -3
  •   Cross    12 年前

    所有这些都是有效的答案,避免指针,使用复制构造函数等。除非您需要创建一个需要良好性能的程序,根据我的经验,大多数与性能相关的问题都与复制构造函数有关,以及它们引起的开销。(而且智能指针在这个字段上并没有更好的效果,我要删除所有的boost代码并手动删除,因为它需要花费太多的毫秒来完成它的工作)。

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

    也许我会得到一些负面的观点,但我认为你需要得到完整的图片来做你的设计选择。

    我认为,“如果你返回指针,你的设计是错误的”这句话几乎没有误导性。输出参数往往容易混淆,因为它不是“返回”结果的自然选择。

    我知道这个问题由来已久,但我没有看到其他任何论据指出这种设计选择的性能开销。