C++ 我怎样才能使我的班级对“免疫”;自动值=代理的副本;C+中的地雷+;?
我正在开发一个相当复杂的数学库,当客户端代码使用auto时,我发现了一个讨厌的bug。在创建一个最小的繁殖案例并提出一个问题的过程中,我意识到我可以单独使用标准库复制类似的东西。请参阅这个简单的测试用例:C++ 我怎样才能使我的班级对“免疫”;自动值=代理的副本;C+中的地雷+;?,c++,copy-assignment,C++,Copy Assignment,我正在开发一个相当复杂的数学库,当客户端代码使用auto时,我发现了一个讨厌的bug。在创建一个最小的繁殖案例并提出一个问题的过程中,我意识到我可以单独使用标准库复制类似的东西。请参阅这个简单的测试用例: #include <vector> #include <assert.h> int main() { std::vector<bool> allTheData = {true, false, true}; auto boolValue =
#include <vector>
#include <assert.h>
int main()
{
std::vector<bool> allTheData = {true, false, true};
auto boolValue = allTheData[1]; // This should be false - we just declared it.
assert(boolValue == false);
boolValue = !boolValue;
assert(boolValue == true);
assert(allTheData[1] == false); // Huh? But we never changed the source data! Only our local copy.
}
这里我只使用向量,因为这是一个非常简单的问题示例。这不是vector的bug,这是代理类型模式的bug,vector就是其中的一个例子
奇怪的是,MSVC的Intellisense引擎有时会报告将不移动不复制代理类型复制为编译错误,但无论如何都会编译得很好:
如果这个intellisense编译错误是一个真正的编译错误,那就太好了。唉是的,这确实是个问题。在当前的C++(C++ 20中),除了在调用站点上更改代码,这是不理想的,否则没有解决办法。 有一项提案(2017年)试图解决这一确切问题。它使用数学库中的代理类作为示例,就像您的案例一样,并给出了与
std::vector
类似的示例。它给出了更多的例子来说明这种模式带来的问题
本文为此提出了3种解决方案,均以语言实现:
- 运算符符号:
class product_expr { matrix operator auto() { ... } };
- 使用声明:
class product_expr { using auto = matrix; };
- 衰变的专门化:
将
定义为auto x=expr
然后使用used可以专门化typename std::decay::type x=expr
std::decay
#include <cstdio>
#include <utility>
auto someComplexMethod()
{
struct s
{
void operator=(int A)&& {std::printf("Setting A to %i", A);}
};
return s();
}
int main()
{
someComplexMethod() = 4; // Compiles. Yay
auto b = someComplexMethod();
// Unfortunately that still compiles, and it's still taking a
// copy of the proxy, but no damage is done yet.
b = 5;
// That doesn't compile. Error given is:
// No overload for '=' note: candidate function not viable:
// expects an rvalue for object argument
std::move(b) = 6;
// That compiles, but is basically casting around the
// protections, aka shooting yourself in the foot.
}
#包括
#包括
自动someComplexMethod()
{
结构
{
void操作符=(int A)&&{std::printf(“将A设置为%i”,A);}
};
返回s();
}
int main()
{
someComplexMethod()=4;//Compiles.Yay
auto b=someComplexMethod();
//不幸的是,它仍在编译,而且仍需要一段时间
//代理的副本,但尚未造成任何损坏。
b=5;
//无法编译。给出的错误是:
//“=”没有重载注意:候选函数不可行:
//对象参数需要一个右值
标准:移动(b)=6;
//它可以编译,但基本上是围绕
//保护,也就是射中自己的脚。
}
我有一个模糊的想法,不确定它有多实用。它不会覆盖auto
推断的结果(这似乎不可能),只会导致将代理复制到格式不正确的变量
- 使代理不可复制
- 由于强制RVO,仅此一点并不能阻止您将代理保存到
变量。为了抵消它,您通过引用返回代理auto
- 为了避免获得悬空引用,可以在默认函数参数中构造代理,该参数与常规参数具有相同的生存期,直到完整表达式结束
- 用户仍然可以保存对代理的引用。为了防止这种误用,您需要返回一个右值引用,并且
-限定所有成员函数&&
- 这将阻止与悬挂代理引用的任何交互,除非您
它。这应该足够模糊以阻止用户,但如果不是,则必须依赖某种消毒剂或在代理的析构函数中设置(volatile?)标志,并在每次访问它时检查它(这是UB,但应该足够可靠)std::move
namespace impl
{
class Proxy
{
Proxy() {}
public:
static Proxy construct() {return {};}
Proxy(const Proxy &) = delete;
Proxy &operator=(const Proxy &) = delete;
int *ptr = nullptr;
int operator=(int value) && {return *ptr = value;}
};
}
impl::Proxy &&make_proxy(int &target, impl::Proxy &&proxy = impl::Proxy::construct())
{
proxy.ptr = ⌖
return std::move(proxy);
}
然后:
重载运算符(除了()
)不能有默认参数之外)更难实现这一点。但仍然可行:
namespace impl
{
struct Index
{
int value = 0;
Proxy &&proxy;
Index(int value, Proxy &&proxy = Proxy::construct()) : value(value), proxy(std::move(proxy)) {}
Index(const Index &) = delete;
Index &operator=(const Index &) = delete;
};
}
struct B
{
int x = 0;
impl::Proxy &&operator[](impl::Index index)
{
index.proxy.ptr = &x;
return std::move(index.proxy);
}
};
唯一的缺点是,由于任何参数最多允许一次用户定义的隐式转换,因此此
运算符[]
仅适用于int
参数,而不适用于具有运算符int
的类。您能给我们展示实际的代表性代码吗?删除复制构造函数通常会起作用。听起来在你的例子中,我们需要进一步约束构造函数accept@largest_prime_is_463035818是的,我知道这一点。我指出“我这里只使用向量,因为它是这个问题的一个非常简单的例子”。我只需要一个简单的问题示例来给出一个最小的可重复性示例,使用该示例只是为了让我能够以5行而不是100行的方式在可重复性案例中显示问题。@AndyG完整的独立示例,不使用std::vector来简化@Maximum_prime_is_463035818的问题。OP已经说明了他/她的用例:proxy在数学图书馆上课。这是数学库中处理向量和矩阵等大型数据操作的常见模式。所以请不要挂断在std::vector
上,因为这个问题有真正的价值,实际上根本不是关于std::vector
。根本问题是,auto
是邪恶的:它隐藏了关于所声明变量的重要事实。在这种情况下,boolValue
是一个引用,但其他位如t
int x = 0;
make_proxy(x) = 1; // Works.
auto a = make_proxy(x); // error: call to deleted constructor of 'impl::Proxy'
auto &b = make_proxy(x); // error: non-const lvalue reference to type 'impl::Proxy' cannot bind to a temporary of type 'impl::Proxy'
const auto &c = make_proxy(x); // Compiles, is a dangling reference. BUT!
c = 2; // error: no viable overloaded '='
auto &&d = make_proxy(x); // Compiles, is a dangling reference.
d = 3; // error: no viable overloaded '='
std::move(d) = 2; // Compiles, causes UB. Needs a runtime check.
namespace impl
{
struct Index
{
int value = 0;
Proxy &&proxy;
Index(int value, Proxy &&proxy = Proxy::construct()) : value(value), proxy(std::move(proxy)) {}
Index(const Index &) = delete;
Index &operator=(const Index &) = delete;
};
}
struct B
{
int x = 0;
impl::Proxy &&operator[](impl::Index index)
{
index.proxy.ptr = &x;
return std::move(index.proxy);
}
};