如何防止使用临时调用构造函数

How to prevent a constructor from being called with a temporary

本文关键字:调用 构造函数 何防止      更新时间:2023-10-16

我有一个类,它的构造函数接受一些向量并存储它们。

struct X {
    X(std::vector<int> const& ints, std::vector<float> const& floats, std::vector<std::string> const& strings)
    : ints_{ints}
    , floats_{floats}
    , strings_{strings}      
    {};
    std::vector<int> ints_;
    std::vector<float> floats_;
    std::vector<std::string> strings_;
};

我想将成员变量变成引用,因为在生产代码中传递给构造函数的值是左值,其生存期比类 X 的对象长。

但是,单元测试通常使用临时函数构造X,如下所示:

X x{ {42}, {3.14f}, {"hello"} };

如果要引用X的成员,则应防止此类调用。这可以通过编写一个接受右值引用的构造函数并使其=delete来完成。

X(std::vector<int> && ints, std::vector<float> && floats, std::vector<std::string> && strings) = delete;

如果所有参数都是临时的,这将阻止实例化。不幸的是,它允许至少一个参数为左值的调用:

std::vector<std::string> no_strings;
X x{ {42}, {3.14f}, no_strings };

由于左值引用愿意绑定到右值,因此可以调用(const&,const&,const&)构造函数。

我是否必须编写左值/右值引用参数的每个组合(所有七个(并将其全部标记为已删除?

template<class T, class U>
using is_lvalue_reference_to_possibly_const = std::integral_constant<bool,
    std::is_same<T, const U&>::value || std::is_same<T, U&>::value>;
template<class VI, class VF, class VS>
using check_value_cat_for_ctor = typename std::enable_if<
    is_lvalue_reference_to_possibly_const<VI, std::vector<int>> &&
    is_lvalue_reference_to_possibly_const<VF, std::vector<float>> &&
    is_lvalue_reference_to_possibly_const<VS, std::vector<string>>>::type;
template<class VI, class VF, class VS, class = check_value_cat_for_ctor<VI, VF, VS>>
X(VI&&, VF&&, VS&&) {
    // ...
}

带有非常量左值引用参数的函数怎么样?临时人员无法绑定到这些,因此这应该是您正在寻找的。

像这样:

#include <string>
#include <vector>
struct X {
    X(std::vector<int>& ints,
      std::vector<float>& floats,
      std::vector<std::string>& strings)
    : ints_{ints}
    , floats_{floats}
    , strings_{strings}      
    {};
    std::vector<int>& ints_;
    std::vector<float>& floats_;
    std::vector<std::string>& strings_;
};
int main()
{
    X x{ {42}, {3.14f}, {"hello"} };
}

你唯一失去的是一点const正确性,它可以像构造函数一样薄。也就是说,如果你想要const的话。你的问题根本没有说清楚。


因为 @T.C. 答案中的模板让我的眼睛流血,如果你更倾向于该解决方案,这是我的版本:

#include <string>
#include <type_traits>
#include <vector>
template<typename ... Ts>
constexpr bool are_not_temporaries = (std::is_lvalue_reference<Ts>::value&&...);
struct X
{
  template<typename VI, typename VF, typename VS,
           typename = std::enable_if_t<are_not_temporaries<VI, VF, VS>, void>>
  X(VI&& ints, VF&& floats, VS&& strings)
    : ints_{ints}
    , floats_{floats}
    , strings_{strings}      
    {};
    std::vector<int>& ints_;
    std::vector<float>& floats_;
    std::vector<std::string>& strings_;
};
int main()
{
    X x{ {42}, {3.14f}, {"hello"} };
}

这将使用 C++17 折叠表达式在可变参数模板参数上扩展参数包。如果 C++17 不可用,您可以将其替换为:

template<typename T1, typename T2, typename T3>
constexpr bool are_not_temporaries = std::is_lvalue_reference<T1>::value 
                                  && std::is_lvalue_reference<T2>::value 
                                  && std::is_lvalue_reference<T3>::value;

诚然,这不太好。

如果你只是听从库怎么办:

template <typename T>
using vector_ref = std::reference_wrapper<std::vector<T> const>;
struct X {
  X(vector_ref<int> ints, vector_ref<float> floats, vector_ref<std::string> strings);
};

std::reference_wrapper已经只能从左值构造,所以我们不需要自己做所有这些工作。