Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/cplusplus/144.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ 我怎样才能使我的班级对“免疫”;自动值=代理的副本;C+中的地雷+;?_C++_Copy Assignment - Fatal编程技术网

C++ 我怎样才能使我的班级对“免疫”;自动值=代理的副本;C+中的地雷+;?

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 =

我正在开发一个相当复杂的数学库,当客户端代码使用auto时,我发现了一个讨厌的bug。在创建一个最小的繁殖案例并提出一个问题的过程中,我意识到我可以单独使用标准库复制类似的东西。请参阅这个简单的测试用例:

#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
    定义为
    typename std::decay::type x=expr
    然后使用used可以专门化
    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
    变量。为了抵消它,您通过引用返回代理

  • 为了避免获得悬空引用,可以在默认函数参数中构造代理,该参数与常规参数具有相同的生存期,直到完整表达式结束

  • 用户仍然可以保存对代理的引用。为了防止这种误用,您需要返回一个右值引用,并且
    &&
    -限定所有成员函数

  • 这将阻止与悬挂代理引用的任何交互,除非您
    std::move
    它。这应该足够模糊以阻止用户,但如果不是,则必须依赖某种消毒剂或在代理的析构函数中设置(volatile?)标志,并在每次访问它时检查它(这是UB,但应该足够可靠)

例如:

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 = &target;
    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);
    }
};