我只是在测试查看一些代码并注意到类似的内容:
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;
};
为什么第一种方式不常见?有什么缺点?
答案 0 :(得分:34)
返回适当参考的(内联)函数更好的原因有几个:
引用将需要每个对象中的内存(通常与指针的数量相同)
引用通常与指针具有相同的对齐方式,从而导致周围对象可能需要更高的对齐,从而浪费更多的内存
初始化参考需要(少量)时间
拥有引用类型的成员字段将禁用默认副本并移动赋值运算符,因为引用不可重载
拥有引用类型的成员字段将导致自动生成的默认副本和移动构造函数不正确,因为它们现在将包含对其他对象成员的引用
函数可以执行其他检查,例如在调试版本中验证不变量
请注意,由于内联,该功能通常不会产生超出可能稍大的二进制文件的任何额外费用。
答案 1 :(得分:3)
封装
对于面向对象的编程纯粹主义者,m_value
是一个实现细节。类example
的消费者应该能够使用单个可靠的界面来访问value()
,并且不应该依赖于example
确定它的方式。未来版本的example
(或复杂的模板特化)可能希望在返回value()
之前使用缓存或日志记录;或者由于内存限制,它可能需要动态计算value()
。
如果您最初没有使用访问者功能,那么如果您以后改变主意,使用example
的所有内容可能都需要更改。这可能会引入各种浪费和错误。通过提供value()
之类的访问器,可以更容易地将其进一步抽象化。
另一方面,有些人对这些OOP原则并不那么刻板,只是喜欢编写有效且易读的代码,并在发生重构时处理重构。
答案 2 :(得分:2)
第一个选项需要额外的内存,就像指针一样。
如果你这样做:
inline const T& value() const{
return m_value;
}
您可以获得与第一种方法相同的好处,而无需额外的内存。
此外,由于第一种方法需要C ++ 11,因此人们不太可能使用它。
答案 3 :(得分:1)
返回const引用的一般用法:
在创建或销毁副本的情况下,返回常量引用是很常见的
一般来说,规则是:如果传递和使用引用比制作副本更昂贵,则不要这样做。如果没有要求您提供子对象,通常甚至是不可能的
否则,它是一个有效的性能优化,可以返回常量引用,这对于行为良好的源代码是透明的。
很多模板代码返回常量引用,即使上面的测试没有指出,只是平等地对待所有特化,并且因为函数非常小并且几乎保证无论如何都要内联。
现在,为了你好奇的发现(从未见过它):
+不需要访问器功能(仍然,这是蹩脚的,无论如何都会被编译出来)
- 对象更大(引用通常需要与指针一样多的空间)
- 由于上述原因,可能需要更好的对齐
- 没有魔术成员函数,因为编译器不知道如何复制引用
- 对于调试版本,无法添加其他检查。
同样的外观和感觉没有这种附带损害可以实现像这样的顺便说一句(只有额外的检查调试不可能):
template<typename T>
struct example {
example(T t): value{t}{}
union{
const T value;
struct{
private:
T value;
friend class example<T>;
} _value;
};
};