避免通过操作从私有构造函数间接实例化
Avoid indirect instantiation from private constructor through operation
我正在尝试创建一个 class,其对象必须包含对其值所代表的内容的简短描述 ("name")。因此,唯一的 public 构造函数应该将字符串作为参数。
但是,对于这些操作,我需要创建临时(无相关名称)对象来计算要分配给现有对象的值。为此,我已经实现了一个私有构造函数,不应直接或间接地使用它来实例化一个新对象——这些临时对象应该只通过 operator= 分配给一个已经存在的对象,它只复制值而不是名称和值。
使用 "auto" 时出现问题。如果声明一个新变量如下:
auto newObj = obj + obj;
编译器推导出return类型的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
}
一个快速的解决方法是不 return 来自 operator +
的临时 Sample<T>
。由于您只想要值部分,因此您可以 return 代替。这会将代码更改为
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;
将使 g
无论 T
是什么,并且 g.show();
将无法编译,因为 g
不是 Sample<T>
.
Sample<double> g = a+c;
也不会工作,因为它试图从一个值构造 g
并且该构造函数是私有的。
这需要添加
friend T operator+(T val, Sample<T> rhs) { return val + rhs._value; }
如果你希望能够像
这样链接添加
a + a + a;
我的建议是将 + operator
(或任何其他需要实施的操作)的签名更改为 return 不同的类型。
比添加一个接受此 "different type" 的赋值运算符,但不添加复制构造函数 - 或者,为了更好的错误报告,添加一个 deleted
。
这将需要更多编码,因为您可能还想在此类型上定义 "operations",以便链接有效。
与 NathanOliver 的回答有些相关但也正交:
你在这里混合了不同的概念。从本质上讲,您有 NamedValue
和 Sample
的概念,但您正试图使每个 表达式 由 NamedValue
上的算术形成也是一个NamedValue
。那是行不通的——表达式(根据你的语义)没有名字,所以它不应该是 NamedValue
。所以,有NamedValue operator+(const NamedValue& other)
是没有意义的。
Nathan 的回答通过添加 return T
来解决这个问题。这很简单。
但是,请注意,由于 a + b
必须 有一个类型,您不能阻止 auto g = a + b
编译,即使它显然是不正确的代码。问 Eigen,或任何其他表达式模板库。无论您如何选择 operator+
的 return 类型,这都是正确的。所以很遗憾,你的这个愿望无法实现。
不过,我还是建议您不要将 T
用作 return 类型,而应使用另一个 class,例如 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;
};
这让你可以检查你的每一个操作(因为中间的 +
in (a + b) + (c + d)
不能接受 NamedValue
s,见上文)而不是只有当转换回命名值。
您可以通过仅允许从 Unnamed
临时对象构造 Sample
来稍微提高编译时安全性:https://godbolt.org/g/Lpz1m5
这一切都可以比这里的草图更优雅地完成。请注意,这完全是朝着表达式模板的方向发展。
我正在尝试创建一个 class,其对象必须包含对其值所代表的内容的简短描述 ("name")。因此,唯一的 public 构造函数应该将字符串作为参数。
但是,对于这些操作,我需要创建临时(无相关名称)对象来计算要分配给现有对象的值。为此,我已经实现了一个私有构造函数,不应直接或间接地使用它来实例化一个新对象——这些临时对象应该只通过 operator= 分配给一个已经存在的对象,它只复制值而不是名称和值。
使用 "auto" 时出现问题。如果声明一个新变量如下:
auto newObj = obj + obj;
编译器推导出return类型的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
}
一个快速的解决方法是不 return 来自 operator +
的临时 Sample<T>
。由于您只想要值部分,因此您可以 return 代替。这会将代码更改为
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;
将使 g
无论 T
是什么,并且 g.show();
将无法编译,因为 g
不是 Sample<T>
.
Sample<double> g = a+c;
也不会工作,因为它试图从一个值构造 g
并且该构造函数是私有的。
这需要添加
friend T operator+(T val, Sample<T> rhs) { return val + rhs._value; }
如果你希望能够像
这样链接添加a + a + a;
我的建议是将 + operator
(或任何其他需要实施的操作)的签名更改为 return 不同的类型。
比添加一个接受此 "different type" 的赋值运算符,但不添加复制构造函数 - 或者,为了更好的错误报告,添加一个 deleted
。
这将需要更多编码,因为您可能还想在此类型上定义 "operations",以便链接有效。
与 NathanOliver 的回答有些相关但也正交:
你在这里混合了不同的概念。从本质上讲,您有 NamedValue
和 Sample
的概念,但您正试图使每个 表达式 由 NamedValue
上的算术形成也是一个NamedValue
。那是行不通的——表达式(根据你的语义)没有名字,所以它不应该是 NamedValue
。所以,有NamedValue operator+(const NamedValue& other)
是没有意义的。
Nathan 的回答通过添加 return T
来解决这个问题。这很简单。
但是,请注意,由于 a + b
必须 有一个类型,您不能阻止 auto g = a + b
编译,即使它显然是不正确的代码。问 Eigen,或任何其他表达式模板库。无论您如何选择 operator+
的 return 类型,这都是正确的。所以很遗憾,你的这个愿望无法实现。
不过,我还是建议您不要将 T
用作 return 类型,而应使用另一个 class,例如 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;
};
这让你可以检查你的每一个操作(因为中间的 +
in (a + b) + (c + d)
不能接受 NamedValue
s,见上文)而不是只有当转换回命名值。
您可以通过仅允许从 Unnamed
临时对象构造 Sample
来稍微提高编译时安全性:https://godbolt.org/g/Lpz1m5
这一切都可以比这里的草图更优雅地完成。请注意,这完全是朝着表达式模板的方向发展。