为什么要使用函数而不是引用成员

Why use a function rather than a reference to member?

本文关键字:引用 成员 函数 为什么      更新时间:2023-10-16

我只是在测试中查看了一些代码,并注意到类似于以下内容:

template<typename T>
class example{
    public:
        example(T t): m_value{t}{}
        const T &value = m_value;
    private:
        T m_value;
};

我以前没见过这个。我以前使用过的几乎每一个API或库都定义了一个函数,该函数返回这样的成员变量,而不是对它的恒定引用:

template<typename T>
class example{
    public:
        example(T t): m_value{t}{}
        const T &value() const{
            return m_value;
        }
    private:
        T m_value;
};

为什么第一种方式不那么常见?缺点是什么?

返回适当引用的(内联)函数更好的原因有几个:

  1. 引用将需要每个对象中的内存(通常与指针的数量相同)

  2. 引用通常与指针具有相同的对齐方式,因此导致周围对象可能需要更高的对齐方式并因此浪费更多的内存

  3. 初始化引用需要(少量)时间

  4. 具有引用类型的成员字段将禁用默认的复制和移动分配运算符,因为引用不可重新放置

  5. 具有引用类型的成员字段将导致自动生成的默认复制和移动构造函数不正确,因为它们现在将包含对其他对象成员的引用

  6. 函数可以进行额外的检查,如在调试构建中验证不变量

请注意,由于内联,函数通常不会产生任何额外的成本,除了可能稍大的二进制文件。

封装。

对于面向对象编程纯粹主义者来说,m_value是一个实现细节。example类的使用者应该能够使用一个可靠的接口来访问value(),而不应该依赖于example如何确定它。example的未来版本(或复杂的模板专用化)可能希望在返回value()之前使用缓存或日志记录;或者可能由于存储器约束而需要动态地计算CCD_ 7。

如果您最初不使用访问器函数,那么如果您以后改变主意,那么使用example的所有内容可能都必须更改。这会带来各种各样的浪费和漏洞。通过提供类似value()的访问器,可以更容易地将其抽象一级。

另一方面,有些人对这些OOP原则并不那么严格,只是喜欢编写高效易读的代码,在重构发生时进行处理。

第一个选项需要额外的内存,就像指针一样。

如果你这样做:

inline const T& value() const{
      return m_value;
}

在不需要额外内存的情况下,您可以获得与第一种方法相同的好处。

此外,由于第一种方法需要C++11,因此人们使用它的可能性较小。

返回常量引用的常规用法:
在创建或销毁副本成本高昂的情况下,返回常量引用是很常见的
一般来说,规则是:如果传递和使用引用比复制更昂贵,不要这样做。如果没有人要求你提供子对象,通常甚至是不可能的
否则,返回常量引用是有效的性能优化,这对行为良好的源代码是透明的
许多模板代码即使在上面的测试没有指示的地方也会返回常量引用,只是为了平等对待所有专业化,因为函数非常小,而且几乎可以保证内联。

现在,对于你好奇的发现(从未见过它的样子):
+不需要访问器函数(不过,这是蹩脚的,无论如何都会编译出来)
-对象更大(引用通常需要与指针一样多的空间)
-由于上述原因,可能需要更好的对齐
-没有神奇的成员函数,因为编译器不知道如何复制引用
-对于调试生成,不能添加其他检查。

相同的外观&感觉没有附带损害可以像这样实现btw(只有额外的调试检查是不可能的):

template<typename T>
struct example {
    example(T t): value{t}{}
    union{
        const T value;
        struct{
        private:
            T value;
            friend class example<T>;
        } _value;
    };
};