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

And*_*zzo 9 c++ constructor overloading private

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

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

问题来自于使用"自动".如果新变量声明如下:

auto newObj = obj + obj;
Run Code Online (Sandbox Code Playgroud)

编译器推导出operator +的返回类型,并直接将其结果赋值给newObj.这会导致对象具有不相关的名称,这不应该是实例化的.

此外,从某些功能仍然可以推断出已存在对象的类型,例如:

auto newObj = obj.makeNewObjWithSameTypeButOtherName("Other name");
Run Code Online (Sandbox Code Playgroud)

按照展示问题的代码:

#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
}
Run Code Online (Sandbox Code Playgroud)

Nat*_*ica 6

一个快速的解决方法是不返回一个临时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;
}
Run Code Online (Sandbox Code Playgroud)

然后

auto g = a+c;
Run Code Online (Sandbox Code Playgroud)

g一切T是与g.show();将无法正常编译,g是不是Sample<T>.

Sample<double> g = a+c;
Run Code Online (Sandbox Code Playgroud)

也不会工作,因为它试图g从值构造并且构造函数是私有的.


这将需要添加

friend T operator+(T val, Sample<T> rhs) { return val + rhs._value; }
Run Code Online (Sandbox Code Playgroud)

如果你想能够链接添加

a + a + a;
Run Code Online (Sandbox Code Playgroud)


Max*_*hof 2

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

您在这里混合了不同的概念。NamedValue本质上,您有with的概念Sample,但您试图使每个表达式也由NamedValuea上的算术形成NamedValue。这是行不通的 - 表达式(根据你的语义)没有名称,所以它不应该是NamedValue. 所以,拥有NamedValue operator+(const NamedValue& other)是没有意义的。

内森的回答通过让添加返回T来解决这个问题。这非常简单。

但是,请注意,由于a + b 必须有一个类型,因此您不能停止auto g = a + b编译,即使它是明显不正确的代码。询问Eigen或任何其他表达式模板库。无论您如何选择 的返回类型,这仍然成立operator+。因此,不幸的是,您的这个愿望无法实现。

不过,我建议您不要使用 plainT作为返回类型,而是使用另一个类,例如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;
};
Run Code Online (Sandbox Code Playgroud)

这允许您在每个操作中进行检查和检查(因为中间+不能(a + b) + (c + d)接受NamedValues,请参见上文),而不是仅在转换回命名值时进行检查。

演示在这里

Sample您可以通过仅允许临时构造来稍微提高编译时安全性Unnamedhttps://godbolt.org/g/Lpz1m5

这一切都可以比这里描绘的更优雅地完成。请注意,这完全朝着表达式模板的方向发展。