我什么时候应该在C ++中使用引用?

时间:2013-07-02 09:51:05

标签: c++ pointers c++03

我一直在编程C ++已经有一段时间了,我开始怀疑规则尽可能使用引用应该在任何地方应用。

this related SO post不同,我对另一种事情感兴趣。

根据我的经验,参考/指针混合会弄乱你的代码:

std::vector<Foo *> &x = get_from_somewhere(); // OK? reference as return value
some_func_pass_by_ref(x); // OK reference argument and reference variable
some_func_by_pointer(x[4]); // OK pointer arg, pointer value
some_func_pass_elem_by_ref(*x[5]); // BAD: pointer value, reference argument
some_func_that_requires_vec_ptr(&x); // BAD: reference value, pointer argument

一种选择是将&替换为* const,将Foo &替换为Foo * const

void some_func_by_ref(const std::vector<Foo * const> * const); // BAD: verbose!
这样,至少遍历已经消失了。而且我重写函数头文件已经不见了,因为所有参数都是指针......代价是用const而不是指针算法(主要是&*)来污染代码。 / p>

我想知道

考虑:

  • 函数原型的最小重写(即:哦,该死的我需要重写很多原型,因为我想将这个引用的元素放入容器中)
  • 提高可读性

    • 避免应用*Foo*转换为Foo&,反之亦然
    • 避免在* const
    • 中使用过多的const

注意:我想到的一件事是每当我打算将元素放入STL容器时使用指针(参见boost :: ref)

我认为这不是C ++ 03特有的,但是如果它们可以被反向移植到C ++ 03(即:NRVO而不是移动语义),那么C ++ 11解决方案就没问题了。

3 个答案:

答案 0 :(得分:7)

  

我什么时候应该在C ++中使用引用?

当你需要处理像对象本身这样的变量时(大多数情况下你没有明确需要指针而不想取得对象的所有权)。

  

我想知道如何以及何时应用尽可能使用引用规则。

尽可能,除非您需要

  • 处理地址(记录地址,诊断或编写自定义内存分配等)
  • 获取参数的所有权(按值传递)
  • 尊重需要指针的接口(C互操作性代码和遗留代码)。

Bjarne Stroustrup在他的书中指出,他引入了对该语言的引用,因为需要在不制作对象副本的情况下调用运算符(这意味着“通过指针”),并且他需要遵循类似于按值调用的语法(这意味着“不是通过指针”)(因此引用已经诞生)。

简而言之,你应该尽可能少地使用指针:

  • 如果值是可选的(“可以为null”),那么在它周围使用std :: optional,而不是指针
  • 如果您需要获取值的所有权,请按值(而非指针)接收参数
  • 如果您需要在不修改值的情况下读取值,请按const &
  • 接收参数
  • 如果您需要动态分配或返回新的/动态分配的对象,请通过以下方式之一传输值:std::shared_ptrstd::unique_ptryour_raii_pointer_class_here - 而不是(原始)指针
  • 如果你需要传递指向C代码的指针,你仍然应该使用std :: xxx_ptr类,并使用.get()获取指针以获取原始指针。
  

我想到的一件事是每当我打算将元素放入STL容器时使用指针(或者我可以摆脱它吗?)

您可以使用Boost Pointer Container library

答案 1 :(得分:2)

恕我直言,因为原始指针很危险,因为所有权和销毁责任很快就不清楚了。因此围绕概念的多个封装(smart_ptrauto_ptrunique_ptr,...)。 首先,考虑在容器中使用这种封装而不是原始指针。

其次,为什么需要将指针放在容器中?我的意思是,它们意味着包含完整的物体;毕竟,他们有一个allocator作为模板参数,用于精确的内存分配。大多数情况下,你需要指针,因为你有一个OO方法大量使用多态。你应该重新考虑这种方法。例如,您可以替换:

struct Animal {virtual std::string operator()() = 0;};
struct Dog : Animal {std::string operator()() {return "woof";}};
struct Cat : Animal {std::string operator()() {return "miaow";}};
// can not have a vector<Animal>

通过类似的方式,使用Boost.Variant

struct Dog {std::string operator()() {return "woof";}};
struct Cat {std::string operator()() {return "miaow";}};
typedef boost::variant<Dog, Cat> Animal;
// can have a vector<Animal>

这种方式当您添加新动物时,您不会继承任何内容,只需将其添加到变体中即可。

您还可以使用Boost.Fusion来考虑更复杂但更通用的内容:

struct Dog {std::string talk; Dog() : talk("wook"){}};
struct Cat {std::string talk; Cat() : talk("miaow"){}};

BOOST_FUSION_ADAPT_STRUCT(Dog, (std::string, talk))
BOOST_FUSION_ADAPT_STRUCT(Cat, (std::string, talk))

typedef boost::fusion::vector<std::string> Animal;

int main()
{
    vector<Animal> animals;
    animals.push_back(Dog());
    animals.push_back(Cat());

    using boost::fusion::at;
    using boost::mpl::int_;

    for(auto a : animals)
    {
        cout << at<int_<0>>(a) << endl;
    }
}

通过这种方式,您甚至不需要修改类似变体的聚合,也不需要修改动物上的算法,您只需要提供与所使用的算法先决条件匹配的FUSION_ADAPT。两个版本(变体和融合)都允许您定义正交对象组,这是继承树无法做到的有用的事情。

答案 2 :(得分:0)

以下方法似乎对此有合理处理:

  • boost和C ++ 11有一个可以廉价地用于在容器中存储引用的类:Reference Wrapper
  • 一个好的建议是更频繁地使用handle/body idiom而不是传递原始指针。这也解决了由引用或指针控制的内存的所有权问题。来自Adobe的Sean Parent 2013年本土的演讲中指出了这一点。

我选择使用Handle / Body Idiom方法,因为它在隐藏底层实现和所有权语义的同时提供指针自动复制/分配行为。它还可以作为一种编译时防火墙,减少头文件包含。