我正在尝试创建一个类,该类的对象必须包含对其值表示的简短描述(“名称”)。因此,唯一的公共(public)构造函数应采用字符串作为参数。
但是,对于这些操作,我需要创建临时(无相关名称)对象以计算要分配给已存在的对象的值。为此,我实现了一个私有(private)构造函数,不应直接或间接地使用它来实例化一个新对象-这些临时对象仅应通过operator =分配给一个已经存在的对象,该操作符仅复制值而不是名称和值(value)。
问题来自“自动”的使用。如果新变量声明如下:
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
}
最佳答案
与NathanOliver的答案有些相关,但也与之正交:
您在这里混合了不同的概念。从本质上讲,您具有NamedValue
和Sample
的概念,但是您正在尝试使由NamedValue
上的算术形式形成的每个表达式也成为NamedValue
。那是行不通的-表达式(根据您的语义)没有名称,因此它不应是NamedValue
。因此,拥有NamedValue operator+(const NamedValue& other)
没有意义。
Nathan的答案通过使附加内容返回T
来解决此问题。那很简单。
但是,请注意,由于a + b
必须具有类型,因此即使显示明显不正确的代码,也无法停止auto g = a + b
的编译。询问Eigen或任何其他表达模板库。无论您如何选择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)
无法接受NamedValue
,请参见上文),而不是仅在转换回命名值时进行检查。Demo here。
通过仅允许从
Sample
临时对象构造Unnamed
,可以稍微提高编译时的安全性:https://godbolt.org/g/Lpz1m5所有这些都可以比此处所描绘的更为优雅地完成。请注意,这确实是朝表达式模板的方向移动。