如果仅声明复制构造函数,如何创建第一个对象?

How to create the first object if only copy constructor is declared?

今天在网上看到一个C++03的例子

class Cat {
    public:
        Cat(const Cat& iCat);
};

有人告诉我,在这种情况下,编译器不会自动生成默认构造函数。如果为真,则意味着可以从现有的 Cat 对象创建新的 Cat 对象。

谁能告诉我在这种情况下如何创建第一个 Cat 对象?或者如果我的理解有误请指正。

你可以这样做,尽管完全不推荐;做下面显示的是非常非常糟糕的做法,会导致"Undefined behavior"

 const Cat* pCat = nullptr;
 const Cat& cat = *pCat; // yikes! NULL reference...boo!
 Cat fluffy(cat);

如果你正在尝试做这样的事情,你可能以完全错误的方式来解决这个问题,你应该重新思考 你的解决方案。

不要在生产代码中执行以下操作,但为了说明,以下操作适用于 "gcc 5.4.0"。

由于涉及 UB(未定义行为),因此在此处使用 std::string,例如作为 cat 的成员变量,示例被简化为:

为了使 'cat' 栩栩如生,您可以进行一些重新解释来创建您的 'first' 猫:

class Cat {
    public:
        Cat(const Cat& iCat) {
        }
};

int main() {        
    Cat* cat = reinterpret_cast<Cat*>(new char[sizeof(Cat)]);
    Cat cat2 = Cat(*cat);
    delete cat;
}

再次强调,以上代码仅用于说明目的,仅适用于 gcc 5.4.0,不保证它适用于其他编译器。

有机会在代码中轻松引入 UB,方法是按照评论中的说明对其进行扩展,例如:

#include <iostream>

class Cat {
    private:
        std::string name_;
    public:
        Cat(const Cat& iCat) {
           this->name_ = iCat.name_;
        }
        void setName(const std::string& name) { name_ = name; }
        const std::string& name() { return name_; }
};

int main() {        
    Cat* cat = reinterpret_cast<Cat*>(new char[sizeof(Cat)]);
    cat->setName("Lilly");
    Cat cat2 = Cat(*cat);
    std::cout << cat2.name() << std::endl;
    delete cat;
}

Can anyone tell me how to create the first Cat object in this case? or kindly correct me if my understanding is wrong.

这里唯一有效的答案是:

没有办法只使用您发布的代码。您可能错过了与您看到的示例一起提供的一些附加功能。


如果声明了任何其他构造函数,还有一些方法 private,例如:

class Cat {
    public:
        Cat(const Cat& iCat);
        static Cat* CreateCat(const std::string& color) {
            return new Cat(color);
        }
    private:
        Cat(const std::string& color)
};

首先,正确答案应该是没有办法创建第一个Cat。该示例可能只是为了演示用户声明的构造函数将如何防止隐式声明的默认构造函数被声明而被拉出。


现在,尽管如此,并且纯粹是手段证明了目的,但是,有一些方法可以使用 布局兼容 对象来创建第一只猫。

C++11 引入了布局兼容性:

Two standard-layout struct (Clause 9) types are layout-compatible if they have the same number of non-static data members and corresponding non-static data members (in declaration order) have layout-compatible types (3.9).


A standard-layout class is a class that:

  • has no non-static data members of type non-standard-layout class (or array of such types) or reference,
  • has no virtual functions (10.3) and no virtual base classes (10.1),
  • has the same access control (Clause 11) for all non-static data members,
  • has no non-standard-layout base classes,
  • either has no non-static data members in the most derived class and at most one base class with non-static data members, or has no base classes with non-static data members, and *has no base classes of the same type as the first non-static data member.

A standard-layout struct is a standard-layout class defined with the class-key struct or the class-key class.

这意味着您可以:

class Cat {
public:
    Cat(const Cat& iCat) : x{iCat.x} {}
    int x;
};

class foo {
public:
    foo(int x) : m_x{x} {}
    int m_x;
};

int main() {
    foo f{5};
    Cat* c1 = reinterpret_cast<Cat*>(&f);
    Cat c2 = *c1;
    std::cout << c2.x << std::endl; // 5
}

xm_x 成员用于演示(布局兼容)成员的复制。

注意:如 评论中所述,您可能需要在编译器中禁用严格别名才能使其正常工作。

利用 C++14 [class.mem]/18:

If a standard-layout union contains two or more standard-layout structs that share a common initial sequence, and if the standard-layout union object currently contains one of these standard-layout structs, it is permitted to inspect the common initial part of any of them. Two standard-layout structs share a common initial sequence if corresponding members have layout-compatible types and either neither member is a bit-field or both are bit-fields with the same width for a sequence of one or more initial members.

Cat 是标准布局,因此我们可以创建一个包含两种公共初始序列的并集。任何两个没有数据成员的标准布局 类 都满足具有共同初始序列的条件,因此:

class Cat {
    public: Cat(const Cat& iCat);
};

class Dog {
    public: Dog();
};

union CatDog
{
    Dog dog;
    Cat cat;
};

int main()
{
    CatDog horse{};
    Cat cat(horse.cat);
}

注:标准中没有准确定义"inspect the common initial part"的含义。如果公共初始部分与整个结构重合,是否意味着可以像我的代码中那样检查整个结构?我想这是语言律师的问题。