4

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

这篇相关的 SO 帖子不同,我对不同类型的事物感兴趣。

以我的经验,参考/指针混合会弄乱您的代码:

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

一种选择是替换&* constlike Foo &withFoo * const

void some_func_by_ref(const std::vector<Foo * const> * const); // BAD: verbose!

这样至少遍历消失了。const而我重写函数头已经消失了,因为所有参数都将是指针......以污染代码而不是指针算术(主要是&and *)为代价。

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

考虑:

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

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

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

我不认为这是 C++03 特有的,但如果 C++11 解决方案可以向后移植到 C++03(即:NRVO 而不是移动语义),则它们很好。

4

3 回答 3

7

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

当您需要像对待对象本身一样对待变量时(大多数情况下,您不需要明确需要指针并且不想获得对象的所有权)。

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

尽可能,除非您需要

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

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

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

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

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

您可以使用Boost Pointer Container library

于 2013-07-02T10:51:44.433 回答
2

恕我直言,该规则之所以成立,是因为原始指针很危险,因为所有权和销毁责任很快变得不明确。因此,围绕概念 ( smart_ptr, auto_ptr, unique_ptr, ...) 进行了多重封装。首先,考虑在容器中使用这样的封装而不是原始指针。

其次,为什么需要将指针放入容器中?我的意思是,它们应该包含完整的对象;毕竟他们有一个分配器作为模板参数,用于精确的内存分配。大多数时候,您需要指针,因为您有一种 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。两个版本(变体和融合)都允许您定义正交对象组,这是继承树无法做到的有用的事情。

于 2013-07-02T10:03:12.000 回答
0

以下方法似乎是合理的处理这个问题:

  • boost 和 C++11 有一个类可以廉价地用于在容器中存储引用:Reference Wrapper
  • 一个好的建议是更频繁地使用句柄/正文习语,而不是传递原始指针。这也解决了由引用或指针控制的内存的所有权问题。Adobe 的Sean Parent在go native 2013的一次演讲中指出了这一点。

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

于 2013-11-04T21:00:38.840 回答