避免通过操作从私有构造函数间接实例化

时间:2018-08-17 14:50:08

标签: c++ constructor overloading private

我正在尝试创建一个类,该类的对象必须包含对其值表示的简短描述(“名称”)。因此,唯一的公共构造函数应使用字符串作为参数。

但是,对于操作,我需要创建临时(无相关名称)对象以计算要分配给已经存在的对象的值。为此,我实现了一个私有构造函数,不应直接或间接地使用它来实例化一个新对象-这些临时对象仅应通过operator =分配给一个已经存在的对象,该操作符仅复制值而不是名称和值。

问题来自“自动”的使用。如果新变量声明如下:

auto newObj = obj + obj;

编译器推断出operator +的返回类型,并将其结果直接分配给newObj。这样会导致对象名称不相关,因此无法实例化。

此外,仍然应该可以从某些函数中推断出已经存在的对象的类型,例如:

auto newObj = obj.makeNewObjWithSameTypeButOtherName("Other name");

下面的代码演示了该问题:

#include <iostream>
#include <string>

using namespace std;

template<class T>
class Sample
{
    public:
    Sample(const string&);

    Sample<T> makeNewObj(const string&);
    // Invalid constructors
    Sample();
    Sample(const Sample&);

    void operator=(const Sample&);
    void operator=(const T&);

    Sample<T> operator+(const Sample&) const;

    void show(void);

private:
// Private constructor used during operations
Sample(const T&);

T _value;
string _name;


};

template<class T>
Sample<T>::Sample(const string& name)
{
    this->_name = name;
    this->_value = 0;
}

template<class T>
Sample<T>::Sample(const T&value)
{
    this->_name = "Temporary variable";
    this->_value = value;
}

template<class T>
Sample<T>
Sample<T>::makeNewObj(const string& name)
{
    return Sample<T>(name);
}

template<class T>
void
Sample<T>::operator=(const Sample& si)
{
    this->_name = this->_name; // Make explicit: Never change the name
    this->_value = si._value;
}

template<class T>
void
Sample<T>::operator=(const T& value)
{
    this->_name = this->_name; // Make explicit: Never change the name
    this->_value = value;
}

template<class T>
Sample<T>
Sample<T>::operator+(const Sample& si) const
{
    // if any of the two values are invalid, throw some error
    return Sample<T>( this->_value + si._value );
}

template<class T>
void
Sample<T>::show(void)
{
    cout << _name << " = " << _value << endl;
}

int main()
{
    Sample<double> a("a"), b("b");
    a = 1; // Sample::operator=(const T&)
    b = 2.2; // Sample::operator=(const T&)
    a.show(); // Output: a = 1
    b.show(); // Output: b = 2.2

    auto c = a.makeNewObj("c"); // Should be possible
    c = a + b; // Sample::operator+(const Sample&) and Sample::operator=(const Sample&)
    c.show(); // Output: c = 3.2

//    Sample<double> d; // Compiler error as expected: undefined reference to `Sample::Sample()'
//    auto f = a; // Compiler error as expected: undefined reference to `Sample::Sample(Sample const&)'

    // This is what I want to avoid - should result in compiler error
    auto g = a+c; // No compiler error: uses the private constructor     Sample::Sample(const T&)
    g.show(); // Output: Temporary variable = 4.2  <-- !! Object with irrelevant name
}

3 个答案:

答案 0 :(得分:6)

一种快速的解决方法是不从Input返回临时Sample<T>。由于只需要值部分,因此可以只返回它。这会将代码更改为

operator +

然后

T operator+(const Sample&) const;

template<class T>
T
Sample<T>::operator+(const Sample& si) const
{
    // if any of the two values are invalid, throw some error
    return  this->_value + si._value;
}

会使auto g = a+c; 成为gT不会编译,因为g.show();不是g

Sample<T>

由于它试图从一个值构造Sample<double> g = a+c; 并且该构造函数是私有的,因此也将不起作用。


这将需要添加

g

如果您希望能够链接类似的内容

friend T operator+(T val, Sample<T> rhs) { return val + rhs._value; }

答案 1 :(得分:2)

我的建议是更改+ operator的签名(或实现的任何其他操作)以返回不同的类型。

比添加接受此“不同类型”的赋值运算符,但不添加副本构造函数-或者,为了更好地报告错误,请添加一个deleted

这将需要更多的编码,因为您可能还希望在此类型上定义“操作”,以使链接有效。

答案 2 :(得分:2)

与NathanOliver的答案有些相关,但也与之正交:

您在这里混合使用不同的概念。您实际上将NamedValueSample结合在一起,但是您试图将由NamedValue上的算术形式形成的每个表达式也设为{{1 }}。那是行不通的-表达式(根据您的语义)没有名称,因此它不应为NamedValue。因此,拥有NamedValue没有意义。

Nathan的答案通过使附加内容返回NamedValue operator+(const NamedValue& other)来解决此问题。那很简单。

但是,请注意,由于T 必须具有类型,因此即使证明是错误的代码,也无法停止a + b的编译。询问Eigen或任何其他表达模板库。无论您如何选择auto g = a + b的返回类型,这都是正确的。因此,不幸的是,您的这个愿望无法实现。

不过,我建议您不要使用普通的operator+作为返回类型,而应该使用另一个类,例如T

Unnamed<T>

这使您可以进行检查以及每项操作的内容(因为template<class T> class Unnamed { public: explicit Unnamed(const T& value) : _value(value) {}; Unnamed<T> operator+(const Unnamed<T>& rhs) const { return Unnamed<T>(_value + rhs._value); } friend Unnamed operator+(const Unnamed& lhs, const Sample<T>& rhs); friend Unnamed operator+(const Sample<T>& lhs, const Unnamed& rhs); private: T _value; }; 中的中间+无法接受(a + b) + (c + d),请参见上文),而不是仅在转换时到命名值。

Demo here

通过仅允许从NamedValue个临时对象https://godbolt.org/g/Lpz1m5来构建Sample,可以稍微提高编译时的安全性

这可以比在此处勾画的更为优雅。请注意,这确实是朝表达式模板的方向移动。