简体   繁体   English

C++ operator= 的奇怪行为即使以另一种方式重载也是如此

[英]C++ Weird behavior of operator= even when overloading it in another way

I want to learn more about C++ and just came along a - in my eyes - very weird behavior.我想了解有关 C++ 的更多信息,并且刚刚出现了 - 在我看来 - 非常奇怪的行为。

Question Summary问题摘要

Since the detailed explanation is a bit long, here is a summary:由于详细解释有点长,这里总结一下:

I've a Container with two member variables, of which only one can be filled with a public constructor.我有一个Container两个成员变量的容器,其中只有一个可以用公共构造函数填充。 Both can be filled only in private.两者都只能私下填写。 I've overloaded the copy and move for operator= in a way that the second data member can't be filled/accessed with the.我已经重载了复制并移动了operator=以使第二个数据成员无法填充/访问。 BUT when returning a container with both member variables filled and writing something like但是当返回一个填充了两个成员变量的容器并编写类似的东西时

Container containerB = Container::returnContainerWithBothMembersFilled();

containerB has both member variables filled. containerB填充了两个成员变量。 This seems weird to me and that's not what I need/want.这对我来说似乎很奇怪,这不是我需要/想要的。

Can someone explain to me how to solve that and why this is happening?有人可以向我解释如何解决这个问题以及为什么会这样吗?

Detailed Explanation详细说明

In order to explain the behavior in more detail, here is a more detailed setup that reproduces my behavior:为了更详细地解释该行为,这里有一个更详细的设置来重现我的行为:

Let's assume a class Container with two private members dataA_ and dataB_ of type std::vector<int> .让我们假设一个 class Container有两个私有成员dataA_dataB_类型为std::vector<int> There exists a public constructor to initialize dataA_ (but not! dataB_ ) and a private constructor to initialize both private members.存在一个用于初始化dataA_ (但不是! dataB_ )的公共构造函数和一个用于初始化两个私有成员的私有构造函数。

class Container {
public:
  Container() = default;
  Container(const Container& vector) = delete;

  explicit Container(std::vector<int> data) : dataA_(std::move(data)) {}

  // ... 

private:
  std::vector<int> dataA_;
  std::vector<int> dataB_;

  explicit Container(std::vector<int> dataA, std::vector<int> dataB)
      : dataA_(std::move(dataA)), dataB_(std::move(dataB)) {}
};

Further, there is a function static Container func() that returns an instance of Container with both data members set to some value.此外,还有一个 function static Container func()返回一个Container的实例,其中两个数据成员都设置为某个值。

  static Container func() {
    return Container(std::vector<int>(3,1), std::vector<int>(3,2));
  }

I have overloaded operator= in 2 different variants:我在 2 个不同的变体中重载了operator=

  • Container& operator=(const Container& other)
  • Container& operator=(Container&& other)

where all 2 variants have the same definition shown here for the first overload exemplary:对于第一个重载示例,所有 2 个变体具有此处显示的相同定义:

  Container& operator=(const Container& other) {
    if (this != &other) {
      dataA_ = other.dataA_;
    }
    return *this;
  }

FYI: all code above is written within class Container {... };仅供参考:上面的所有代码都写在class Container {... }; !

Question问题

Why does the following give me a container2 with a 'filled' and not empty dataB_ ?为什么以下内容给我一个带有“已填充”而不是空dataB_container2

int main() {
  std::cout << "init:" << std::endl;
  Container container1(std::vector<int>({1, 2, 3}));
  Container container2 = container1.func(); // doesnt work as expected
  Container container3;
  container3 = container1; // works, overloaded operator= is called and behavior is as expected

  return 0;
}
Container container2 = container1.func();

This is copy-initialization, not assignment, so operator= is irrelevant.这是复制初始化,而不是赋值,因此operator=无关紧要。

Before C++17 the initialization would be ill-formed, because it requires a constructor to be available which accepts a Container rvalue as argument (the result of the call container1.func() ).在 C++17 之前,初始化格式不正确,因为它需要一个可用的构造函数,该构造函数接受Container右值作为参数(调用container1.func()的结果)。 But you deleted the copy constructor and as a consequence there is also no move constructor either.但是您删除了复制构造函数,因此也没有移动构造函数。

Since C++17 the initialization uses mandatory copy elision, meaning that the return value of container1.func() is directly constructed into container2 (because container1.func() is a prvalue of the same class type as container2 ).由于 C++17 初始化使用强制复制省略,这意味着container1.func()的返回值直接构造到container2 (因为container1.func()是与container2相同的 class 类型的纯右值)。 There is no constructor call involved either.也不涉及构造函数调用。 You just get the return value from container1.func() in container2 and that one is constructed with both members filled.您只需从 container2 中的container2 container1.func()获取返回值,并且该返回值是由两个成员都填充的。

Before C++17, even if the copy constructor was not deleted, but defined equiavalently to your operator= , it would still not work the way you want it to because the compiler was still allowed , but not required to elide the move/copy constructor call as is now mandatory since C++17.在 C++17 之前,即使复制构造函数没有被删除,而是与您的operator=等效定义,它仍然无法按照您希望的方式工作,因为仍然允许编译器,但不需要将移动/复制构造函数调用省略为现在是强制性的,因为 C++17。 It would be up to the compiler whether or not container2 would have the second member empty. container2是否将第二个成员为空取决于编译器。 That's one of the reasons why it is a bad idea to change the semantics of the copy/move operations.这就是为什么改变复制/移动操作的语义是一个坏主意的原因之一。 This is one of a small number of situations where the compiler is allowed to perform optimizations which change the observable behavior of the program.这是允许编译器执行改变程序可观察行为的优化的少数情况之一。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM