C++ 为什么=运算符在没有定义的情况下在结构上工作?

C++ 为什么=运算符在没有定义的情况下在结构上工作?,c++,gcc,operators,C++,Gcc,Operators,让我们看一个简单的例子: struct some_struct { std::string str; int a, b, c; } some_struct abc, abc_copy; abc.str = "some text"; abc.a = 1; abc.b = 2; abc.c = 3; abc_copy = abc; 那么abc_copy就是abc的精确副本。。如果不定义=运算符,怎么可能 (在处理某些代码时,这让我感到惊讶。)如果您不自己明确定义一些成员,编译器将为

让我们看一个简单的例子:

struct some_struct {
   std::string str;
   int a, b, c;
}

some_struct abc, abc_copy;
abc.str = "some text";
abc.a = 1;
abc.b = 2;
abc.c = 3;

abc_copy = abc;
那么abc_copy就是abc的精确副本。。如果不定义=运算符,怎么可能


(在处理某些代码时,这让我感到惊讶。)

如果您不自己明确定义一些成员,编译器将为您合成这些成员。赋值运算符就是其中之一。复制构造函数是另一个,你也可以得到一个析构函数。如果不提供任何自己的构造函数,也会得到一个默认构造函数。除此之外,我不知道还有什么,但我相信可能还有其他的(280Z28给出的答案中的链接暗示了其他情况,我不记得我现在在哪里读到过它,所以可能只有四个)。

结构基本上是内存中组件的串联(为了对齐,可能内置了一些填充)。当您将一个结构的值指定给另一个结构时,这些值只需处理。

但它是定义的。在标准中。如果不提供运算符=,则会向您提供一个运算符。默认操作符只是复制每个成员变量。它如何知道复制每个成员的方式?它调用它们的运算符=(如果没有定义,默认情况下会提供该运算符…。

为了保持与C的源代码兼容性,这种行为是必要的

C不给您定义/覆盖运算符的能力,所以结构通常用=操作符复制.< /p>

赋值运算符(<代码>运算符=/COD>)是C++中结构或类的隐式生成函数之一。 下面是一个描述4个隐式生成的成员的引用:

简言之,隐式生成的成员执行一个。以下是链接页面的长版本:

需要时,隐式生成的赋值运算符规范如下所示。规范指出,结果是被分配的对象(
self
),并且在post状态下
self
“的
self
的抽象值的值与来自
的参数
的抽象值的值相同


在C++中,结构等价于成员默认为公共而非私有访问的类。 如果未提供,C++编译器还将自动生成类的以下特殊成员:

  • 默认构造函数-无参数,默认初始化所有内容
  • 复制构造函数-即与类同名的方法,它引用同一类的另一个对象。复制所有值
  • 析构函数-在销毁对象时调用。默认情况下不执行任何操作
  • 赋值运算符-当一个结构/类被赋值给另一个结构/类时调用。这是在上述情况下调用的自动生成的方法

如果不定义这四种方法(C++11中有六种),编译器将为您生成它们:

  • 默认构造函数
  • 复制构造函数
  • 赋值运算符
  • 析构函数
  • 移动构造函数(C++11)
  • 移动赋值(C++11)
如果你想知道原因?
它是为了保持与C的向后兼容性(因为C结构可以使用=和in声明进行复制)。但它也使编写简单类变得更容易。有些人会认为它增加了问题,因为“浅复制问题”我反对的理由是,你不应该有一个拥有原始指针的类。通过使用适当的智能指针,问题就消失了

默认构造函数(如果未定义其他构造函数)

编译器生成的默认构造函数将调用基类默认构造函数,然后调用每个成员的默认构造函数(按声明顺序)

析构函数(如果未定义析构函数)

按声明的相反顺序调用每个成员的析构函数。然后调用基类的析构函数

复制构造函数(如果未定义复制构造函数)

调用传递src对象的基类复制构造函数。然后使用src objects成员作为要复制的值调用每个成员的复制构造函数

赋值运算符

调用传递src对象的基类赋值运算符。然后使用src对象作为要复制的值,对每个成员调用赋值运算符

移动构造函数(如果未定义移动构造函数)

调用传递src对象的基类move构造函数。然后使用src objects成员作为要移动的值来调用每个成员的移动构造函数

移动赋值运算符

调用传递src对象的基类move赋值运算符。然后使用src对象作为要复制的值,对每个成员调用move赋值操作符

如果定义这样的类:

struct some_struct: public some_base
{   
    std::string str1;
    int a;
    float b;
    char* c;
    std::string str2;
};
编译器将构建的是:

struct some_struct: public some_base
{   
    std::string str1;
    int a;
    float b;
    char* c;
    std::string str2;

    // Conceptually two different versions of the default constructor are built
    // One is for value-initialization the other for zero-initialization
    // The one used depends on how the object is declared.
    //        some_struct* a = new some_struct;     // value-initialized
    //        some_struct* b = new some_struct();   // zero-initialized
    //        some_struct  c;                       // value-initialized
    //        some_struct  d = some_struct();       // zero-initialized
    // Note: Just because there are conceptually two constructors does not mean
    //       there are actually two built.

    // value-initialize version
    some_struct()
        : some_base()            // value-initialize base (if compiler generated)
        , str1()                 // has a normal constructor so just call it
        // PODS not initialized
        , str2()
   {}

    // zero-initialize version
    some_struct()
        : some_base()            // zero-initialize base (if compiler generated)
        , str1()                 // has a normal constructor so just call it.
        , a(0)
        , b(0)
        , c(0)   // 0 is NULL
        , str2()
        // Initialize all padding to zero
   {}

    some_struct(some_struct const& copy)
        : some_base(copy)
        , str1(copy.str1)
        , a(copy.a)
        , b(copy.b)
        , c(copy.c)
        , str2(copy.str2)
    {}

    some_struct& operator=(some_struct const& copy)
    {
        some_base::operator=(copy);
        str1 = copy.str1;
        a    = copy.a;
        b    = copy.b;
        c    = copy.c;
        str2 = copy.str2;
        return *this;
    }

    ~some_struct()
    {}
    // Note the below is pseudo code
    // Also note member destruction happens after user code.
    // In the compiler generated version the user code is empty
        : ~str2()
        // PODs don't have destructor
        , ~str1()
        , ~some_base();
    // End of destructor here.

    // In C++11 we also have Move constructor and move assignment.
    some_struct(some_struct&& copy)
                    //    ^^^^  Notice the double &&
        : some_base(std::move(copy))
        , str1(std::move(copy.str1))
        , a(std::move(copy.a))
        , b(std::move(copy.b))
        , c(std::move(copy.c))
        , str2(std::move(copy.str2))
    {}

    some_struct& operator=(some_struct&& copy)
                               //    ^^^^  Notice the double &&
    {
        some_base::operator=(std::move(copy));
        str1 = std::move(copy.str1);
        a    = std::move(copy.a);
        b    = std::move(copy.b);
        c    = std::move(copy.c);
        str2 = std::move(copy.str2);
        return *this;
    } 
};

K&R C根本不允许使用
=
复制结构,我也不确定C89。如果它是在C99中引入的,那么我认为这是因为C++的影响。根据K&R(第二版,1988,第127页),它是由ANSI C引入的,但是大多数现有的编译器已经支持它。默认赋值运算符不能抛出,因为它不分配任何内存。dunno:@Rob:12.8:10开始的默认复制赋值运算符的定义没有提到throw子句。这对我来说很有意义,因为默认的复制赋值操作符可以调用非默认赋值,而非默认赋值可能会抛出。在问题中给出的特定示例中,显然
std::string::operator=(const std::string&)
可以抛出。如果有任何用户定义的构造函数,也不提供隐式默认构造函数。隐式析构函数还调用成员和子对象的析构函数(如果
struct some_struct: public some_base
{   
    std::string str1;
    int a;
    float b;
    char* c;
    std::string str2;

    // Conceptually two different versions of the default constructor are built
    // One is for value-initialization the other for zero-initialization
    // The one used depends on how the object is declared.
    //        some_struct* a = new some_struct;     // value-initialized
    //        some_struct* b = new some_struct();   // zero-initialized
    //        some_struct  c;                       // value-initialized
    //        some_struct  d = some_struct();       // zero-initialized
    // Note: Just because there are conceptually two constructors does not mean
    //       there are actually two built.

    // value-initialize version
    some_struct()
        : some_base()            // value-initialize base (if compiler generated)
        , str1()                 // has a normal constructor so just call it
        // PODS not initialized
        , str2()
   {}

    // zero-initialize version
    some_struct()
        : some_base()            // zero-initialize base (if compiler generated)
        , str1()                 // has a normal constructor so just call it.
        , a(0)
        , b(0)
        , c(0)   // 0 is NULL
        , str2()
        // Initialize all padding to zero
   {}

    some_struct(some_struct const& copy)
        : some_base(copy)
        , str1(copy.str1)
        , a(copy.a)
        , b(copy.b)
        , c(copy.c)
        , str2(copy.str2)
    {}

    some_struct& operator=(some_struct const& copy)
    {
        some_base::operator=(copy);
        str1 = copy.str1;
        a    = copy.a;
        b    = copy.b;
        c    = copy.c;
        str2 = copy.str2;
        return *this;
    }

    ~some_struct()
    {}
    // Note the below is pseudo code
    // Also note member destruction happens after user code.
    // In the compiler generated version the user code is empty
        : ~str2()
        // PODs don't have destructor
        , ~str1()
        , ~some_base();
    // End of destructor here.

    // In C++11 we also have Move constructor and move assignment.
    some_struct(some_struct&& copy)
                    //    ^^^^  Notice the double &&
        : some_base(std::move(copy))
        , str1(std::move(copy.str1))
        , a(std::move(copy.a))
        , b(std::move(copy.b))
        , c(std::move(copy.c))
        , str2(std::move(copy.str2))
    {}

    some_struct& operator=(some_struct&& copy)
                               //    ^^^^  Notice the double &&
    {
        some_base::operator=(std::move(copy));
        str1 = std::move(copy.str1);
        a    = std::move(copy.a);
        b    = std::move(copy.b);
        c    = std::move(copy.c);
        str2 = std::move(copy.str2);
        return *this;
    } 
};