Special member functions (copy/move constructor) - C++11, 21 of n

本文深入探讨了C++中拷贝构造与移动构造的细节,包括它们的默认行为、何时会被隐式声明为删除,以及如何通过两阶段重载解析来选择正确的构造函数。同时,还讨论了拷贝消除的概念及其适用场景。

Copy/move constructor

  • First, what's wrong with this copy constructor ? Infinite recursive ?
    class A {
    public:
        A(A a) {...}
    };
  • If the class definition does not explicitly declare a copy constructor, one is declared implicitly. If the class definition declares a move constructor or move assignment operator, the implicitly declared copy constructor is defined as deleted; otherwise, it is defined as defaulted. [The latter case is deprecated if the class has a user-declared copy assignment operator or a user-declared destructor ???]. Example:
    class A {
    public:
        A() {
        }
        A(A&& a) {
        }
    };

    int main() {
        A a;
        A b(a); // error, A::A(const A&)’ is implicitly declared as
                    // deleted because ‘A’ declares a move constructor 

    }
  • The implicitly-declared copy constructor for a class X will have the form
    X::X(const X&)
    if
    — each direct or virtual base class B of X has a copy constructor whose first parameter is of type const B& or const volatile B&, and
    — for all the non-static data members of X that are of a class type M (or array thereof), each such class type has a copy constructor whose first parameter is of type const M& or const volatile M&. Otherwise, the implicitly-declared copy constructor will have the form X::X(X&)
  • If the definition of a class X does not explicitly declare a move constructor, one will be implicitly declared as defaulted if and only if
    • X does not have a user-declared copy constructor
    • X does not have a user-declared copy assignment operator
    • X does not have a user-declared move assignment operator, and
    • X does not have a user-declared destructor
      Note: When the move constructor is not implicitly declared or explicitly supplied, expressions that otherwise would have invoked the move constructor may instead invoke a copy constructor.
  • A defaulted copy/move constructor for a class X is defined as deleted if X has:
    • a variant member with a non-trivial corresponding constructor and X is a union-like class
    • a non-static data member of class type M (or array thereof) that cannot be copied/moved because overload resolution, as applied to M’s corresponding constructor, results in an ambiguity or a function that is deleted or inaccessible from the defaulted constructor
    • a direct or virtual base class B that cannot be copied/moved because overload resolution as applied to B’s corresponding constructor, results in an ambiguity or a function that is deleted or inaccessible from the defaulted constructor
    • any direct or virtual base class or non-static data member of a type with a destructor that is deleted or inaccessible from the defaulted constructor, or,
    • for the copy constructor, a non-static data member of rvalue reference type
  • The implicitly-defined copy/move constructor for a non-union class X performs a member wise copy/move of its bases and members. Note: brace-or-equal-initializers of non-static data members are ignored. Let x be either the parameter of the constructor or, for the move constructor, an xvalue referring to the parameter. Each base or non-static data member is copied/moved in the manner appropriate to its type
    • if the member is an array, each element is direct-initialized with the corresponding subobject of x
    • if a member m has rvalue reference type T&&, it is direct-initialized with static_cast<T&&>(x.m)
    • otherwise, the base or member is direct-initialized with the corresponding base or member of x.
  • The implicitly-defined copy/move constructor for a union X copies the object representation of X.
  • (COPY ELISION) When certain criteria are met, an implementation is allowed to omit the copy/move construction of a class object, even if the constructor selected for the copy/move operation and/or the destructor for the object have side effects. This elision of copy/move operations, called copy elision, is permitted in the following circumstances (which may be combined to eliminate multiple copies):
    • in a return statement in a function with a class return type, when the expression is the name of a non-volatile automatic object (other than a function or catch-clause parameter [Edit, function parameter or catch-clause parameter doesn't apply COPY ELISION]with the same cv-unqualified type as the function return type, the copy/move operation can be omitted by constructing the automatic object directly into the function’s return value
    • when a temporary class object that has not been bound to a reference would be copied/moved to a class object with the same cv-unqualified type, the copy/move operation can be omitted by constructing the temporary object directly into the target of the omitted copy/move
    • in a throw-expression, when the operand is the name of a non-volatile automatic object (other than a function or catch-clause parameter) whose scope does not extend beyond the end of the innermost enclosing try-block (if there is one), the copy/move operation from the operand to the exception object can be omitted by constructing the automatic object directly into the exception object
    • when the exception-declaration of an exception handler declares an object of the same type (except for cv-qualification) as the exception object, the copy/move operation can be omitted by treating the exception-declaration as an alias for the exception object if the meaning of the program will be unchanged except for the execution of constructors and destructors for the object declared by the exception-declaration.
  • When the criteria for elision of a copy operation are met or would be met save for the fact that the source object is a function parameter,and the object to be copied is designated by an lvalue,overload resolution to select the constructor for the copy is first performed as if the object were designated by an rvalue (WHY ? Because if there is no elision happened, there will be an new object constructed and this new object will be rvalue). If overload resolution fails, or if the type of the first parameter of the selected constructor is not an rvalue reference to the object’s type (possibly cv-qualified), overload resolution is performed again,considering the object as an lvalue. [ Note:This two-stage overload resolution must be performed regardless of whether copy elision will occur. It determines the constructor to be called if elision is not performed, and the selected constructor must be accessible even if the call is elided. —end note ]. Example:

    class Thing {
    public:
        Thing();
        ~Thing();
        Thing(Thing&&);
    private:
        Thing(const Thing&);
    };
    Thing f(bool b) {
        Thing t;
        if (b)
            throw t; // OK: Thing(Thing&&) used (or elided) to throw t
                          // "t" is a lvalue, but treats it as a rvalue when performing overload resolution lookup,
                          // Thing(Thing&&) is picked for moving construction and the move-ctor is defined
                         // and accessible

        return t; // OK: Thing(Thing&&) used (or elided) to return t
    }
    Thing t2 = f(false); // OK: Thing(Thing&&) used (or elided) to construct t2


    class Thing {
    public:
        Thing();
        ~Thing();
        Thing(const Thing&);
    private:
        Thing(Thing&&); 
    };
    Thing f(bool b) {
        Thing t;
        if (b)
            throw t; // ERROR. 
        return t; // ERROR.  When doing overload resolution, treat "t" as rvalue,
                      // so Thing(Thing&&) is picked to do the move construction,
                      // but Thing(Thing&&) is private, not accessible. Hence reporting error.
                      // (This check is performed regardless if copy-elide will happen)

    }
    Thing t2 = f(false); // ERROR


    class Thing {
    public:
        Thing();
        ~Thing();
        Thing(const Thing&);
        Thing(Thing&&) = delete; 
    };
    Thing f(bool b) {
        Thing t;
        if (b)
            throw t; // ERROR. 
        return t; // ERROR.  When doing overload resolution, treat "t" as rvalue,
                      // so Thing(Thing&&) is picked to do the move construction, 

                      // but Thing(Thing&&) is delete, not accessible. Hence reporting error.
                     // (This check is performed regardless if copy-elide will happen)

    }
    Thing t2 = f(false); // ERROR

我们将在你提供的**基础代码框架**上进行修改,完整实现题目要求的功能: --- ### ✅ 需求回顾 1. 添加成员变量: - `string species`(公有) - `int id`(公有) - `static int total`(私有,用于统计对象总数) 2. 构造函数: - 接收 `species` 和 `id` - 每次创建对象时,`total++` 3. 成员函数: - `void printInfo()`:打印格式为 `New animal -> Species: Dog, ID: 1` 4. 所有代码需在原有结构中补全 --- ### ✅ 修改后的完整代码 ```cpp #include <string> #include <iostream> using namespace std; class Animals { public: // 公有成员变量 string species; int id; // 构造函数:接收 species 和 id Animals(const string& s, int i) : species(s), id(i) { total++; // 每次构造对象,总数加1 } // 成员函数:打印信息 void printInfo() { cout << "New animal -> Species: " << species << ", ID: " << id << endl; } private: // 私有静态变量:统计所有实例数量 static int total; }; // 类外定义静态成员变量,并初始化为0 int Animals::total = 0; ``` --- ### 🔍 关键点解释 | 内容 | 说明 | |------|------| | `Animals(const string& s, int i)` | 构造函数接受两个参数,使用引用避免拷贝字符串 | | 初始化列表 `: species(s), id(i)` | 推荐写法,高效且安全 | | `total++` 在构造函数中 | 每次创建对象都计数 | | `static int total;` 放在 `private` | 符合封装原则:外部不能随意修改计数器 | | `int Animals::total = 0;` 必须写在类外 | 否则链接时报错:undefined reference to `Animals::total` | --- ### ✅ 使用示例(评测机逻辑模拟) ```cpp int main() { // 输入数据模拟:("Dog",1), ("Cat",2), ("Bird",3) Animals animals[] = { Animals("Dog", 1), Animals("Cat", 2), Animals("Bird", 3) }; // 调用每个对象的 printInfo() for (int i = 0; i < 3; ++i) { animals[i].printInfo(); } // 检查 total 是否正确 cout << "Total animals: " << Animals::total << endl; return 0; } ``` 输出: ``` New animal -> Species: Dog, ID: 1 New animal -> Species: Cat, ID: 2 New animal -> Species: Bird, ID: 3 Total animals: 3 ``` --- ### ⚠️ 注意事项 - **不要删除原始结构中的 `public:` 和 `private:`** - **不要把 `total` 定义成普通变量**,否则无法统计全局数量 - **必须在类外定义 `static int total`**,这是 C++ 的强制规则 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值