当选择通过const
引用而不是按值传递时,大型类的选择似乎很明确:必须使用const
ref来避免昂贵的副本,因为只有{{3才允许复制省略(如果复制构造函数没有副作用,或者副本来自右值)。
对于limited circumstances来说,通过价值似乎更具吸引力:
那么在编写模板函数时最好的做法是什么,哪个参数的类是大还是小并不总是很明显?
答案 0 :(得分:4)
(假设您只想读取您传递的值。)
我认为最好的选择是通过const&
,因为:
某些对象无法复制,复制成本高昂,或者在复制构造函数中包含副作用。
虽然按const&
采用原语可能会导致性能下降,但与上述要点中描述的问题相比,损失较小。
理想情况下,您可能希望执行类似的操作(我不小心这里的复制构造函数中有副作用的小类):
template <typename T>
using readonly = std::conditional_t<
sizeof(T) <= sizeof(void*),
T,
const T&
>;
template <typename T>
void foo(readonly<T> x);
问题在于T
无法通过对foo
的调用推断出foo<int>(0)
,因为它位于"non-deducible context"。
这意味着您的用户必须拨打foo(0)
而不是T=int
,因为T
无法从编译器中推断出来。
(我想重申一下,我上面使用的条件非常幼稚且有潜在危险。您可能只想检查void*
是原始的还是小于{{1}的POD而不是。)
您可以做的另一件事是使用std::enable_if_t
来控制调用哪个函数:
template <typename T>
auto foo(T x) -> std::enable_if_t<(sizeof(T) <= sizeof(void*))>;
template <typename T>
auto foo(const T& x) -> std::enable_if_t<(sizeof(T) > sizeof(void*))>;
这显然需要大量额外的样板...当(如果?)我们将获得constexpr
块和“元类”时,使用代码生成可能会有更好的解决方案
答案 1 :(得分:2)
我认为,作为基本规则,您应该通过const&
,为一般情况下的昂贵复制对象准备通用模板代码。
例如,如果您查看std::vector
's constructors,在带有计数和值的重载中,该值只是通过const&
传递:
explicit vector( size_type count, const T& value = T(), const Allocator& alloc = Allocator())
答案 2 :(得分:1)
你想要的是一种类型特征,它测试类型是否为 scalar ,然后启用
template <typename Type>
using ConstRefFast = std::conditional_t<
std::is_scalar<std::decay_t<Type>>::value,
std::add_const_t<Type>,
std::add_lvalue_reference_t<std::add_const_t<std::decay_t<Type>>>
>;
然后像这样通过引用传递一个对象
template <typename Type>
void foo(typename ConstRefFast<Type>::type val);
请注意,这意味着该功能将无法再自动推断出T
类型。但在某些情况下,它可能会给你你想要的东西。
请注意,当涉及到模板函数时,有时候所有权问题比仅仅想要通过const ref或值传递值更重要。
例如,考虑一种接受shared_ptr
到某种类型T
并对该指针进行一些处理的方法(在将来的某个时刻或立即),你有两个选项
void insert_ptr(std::shared_ptr<T> ptr);
void insert_ptr(const std::shared_ptr<T>& ptr);
当用户查看这两个函数时,一个人清楚地传达了意义和语义,而另一个只是在他们的脑海中留下了问题。第一个显然在方法开始之前复制指针,从而增加引用计数。但第二个人的语义并不十分清楚。在异步设置中,这可能会在用户的脑海中留下怀疑的余地。我的指针是否会被安全地使用(并且对象被安全地释放),例如,在未来的某个时刻异步使用指向的对象?
您还可以考虑另一种不考虑异步设置的情况。将类型T
的值复制到内部存储
template <typename T>
class Container {
public:
void set_value(T val) {
this->insert_into_storage(std::move(val));
}
};
或
template <typename T>
class Container {
public:
void set_value(const T& val) {
this->insert_into_storage(val);
}
};
这里第一个确实传达了将值复制到方法中的事实,之后容器可能会在内部存储该值。但是如果复制对象的生命周期问题并不重要,那么第二个问题就更有效了,因为它避免了额外移动。
因此,最终归结为您是否需要清晰的API或性能。