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

C++ 如果只声明了复制构造函数,如何创建第一个对象?,c++,copy-constructor,default-constructor,C++,Copy Constructor,Default Constructor,我今天在网上看到了一个C++03示例 class Cat { public: Cat(const Cat& iCat); }; 我被告知在这种情况下,编译器不会自动生成默认构造函数。如果为true,则意味着可以从现有Cat对象创建新的Cat对象 有人能告诉我在这种情况下如何创建第一个Cat对象吗?或者,如果我的理解有误,请纠正我。你可以这样做,尽管这根本不推荐;做下面的事情是非常非常糟糕的做法,会导致未定义的行为 const Cat* pCat = nullp

我今天在网上看到了一个C++03示例

class Cat {
    public:
        Cat(const Cat& iCat);
};
我被告知在这种情况下,编译器不会自动生成默认构造函数。如果为true,则意味着可以从现有Cat对象创建新的Cat对象


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

你可以这样做,尽管这根本不推荐;做下面的事情是非常非常糟糕的做法,会导致未定义的行为

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

如果你正试图做这样的事情,你很可能是以一种完全错误的方式处理问题,你应该重新思考你的解决方案。

你可以这样做,尽管根本不建议这样做;做下面的事情是非常非常糟糕的做法,会导致未定义的行为

 const Cat* pCat = nullptr;
 const Cat& cat = *pCat; // yikes! NULL reference...boo!
 Cat fluffy(cat);
如果您试图这样做,可能是以完全错误的方式处理问题,您应该重新思考您的解决方案。

不要在生产代码中执行以下操作,但在示例中,以下操作适用于gcc 5.4.0

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

为了让“猫”栩栩如生,你可以做一些重新诠释来创造你的“第一只”猫:

班猫{ 公众: Catconst Cat&iCat{ } }; int main{ Cat*Cat=重新解释新字符[sizeofCat]; Cat cat2=Cat*Cat; 删除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;
}
不要在生产代码中执行以下操作,但对于示例,以下操作适用于gcc 5.4.0

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

为了让“猫”栩栩如生,你可以做一些重新诠释来创造你的“第一只”猫:

班猫{ 公众: Catconst Cat&iCat{ } }; int main{ Cat*Cat=重新解释新字符[sizeofCat]; Cat cat2=Cat*Cat; 删除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;
}

有人能告诉我在这种情况下如何创建第一个Cat对象吗?如果我的理解有误,请纠正我

这里唯一有效的答案是:

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

但是,如果任何其他构造函数被声明为私有,则有几种方法,例如:


有人能告诉我在这种情况下如何创建第一个Cat对象吗?如果我的理解有误,请纠正我

这里唯一有效的答案是:

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

但是,如果任何其他构造函数被声明为私有,则有几种方法,例如:


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

现在,尽管如此,而且如果手段证明了目的的正当性,那么有一些方法可以使用与布局兼容的对象创建第一个cat

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

如果两个标准布局结构第9条类型具有相同数量的非静态数据成员,并且声明顺序中相应的非静态数据成员具有布局兼容类型3.9,则它们是布局兼容的

标准布局类是指:

没有此类类型或引用的非标准布局类或数组类型的非静态数据成员, 没有虚拟函数10.3和虚拟基类10.1, 对所有非静态数据成员具有相同的访问控制第11条, 没有非标准布局基类, 在最派生的类中没有非静态数据成员,并且最多有一个基类具有非静态数据成员,或者没有基类具有非静态数据成员,以及 *没有与第一个非静态数据成员类型相同的基类。 标准布局结构是使用类键结构或类键类定义的标准布局类

这意味着您可以执行以下操作:

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
}
x和m_x成员用于演示布局兼容memb的复制 呃


注意:正如在comment by中提到的,您可能需要在编译器中禁用严格别名才能工作。

首先,有效的答案应该是无法创建第一个Cat。这个示例可能只是为了演示用户声明的构造函数如何防止隐式声明的默认构造函数被声明

现在,尽管如此,而且如果手段证明了目的的正当性,那么有一些方法可以使用与布局兼容的对象创建第一个cat

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

如果两个标准布局结构第9条类型具有相同数量的非静态数据成员,并且声明顺序中相应的非静态数据成员具有布局兼容类型3.9,则它们是布局兼容的

标准布局类是指:

没有此类类型或引用的非标准布局类或数组类型的非静态数据成员, 没有虚拟函数10.3和虚拟基类10.1, 对所有非静态数据成员具有相同的访问控制第11条, 没有非标准布局基类, 在最派生的类中没有非静态数据成员,并且最多有一个基类具有非静态数据成员,或者没有基类具有非静态数据成员,以及 *没有与第一个非静态数据成员类型相同的基类。 标准布局结构是使用类键结构或类键类定义的标准布局类

这意味着您可以执行以下操作:

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
}
x和m_x成员用于演示布局兼容成员的复制


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

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

如果标准布局联合包含两个或多个共享公共初始序列的标准布局结构, 如果标准布局联合对象当前包含其中一个标准布局结构,则允许使用它 检查其中任何一个的公共首字母部分。两个标准布局结构共享一个共同的首字母 如果相应的成员具有布局兼容类型,并且两个成员都不是位字段或 对于一个或多个初始成员的序列,这两个字段都是具有相同宽度的位字段

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);
}

注:本标准未准确定义检验通用初始零件的含义。如果公共初始部分与整个结构一致,这是否意味着可以像我的代码中那样检查整个结构?我想这是语言律师的问题。

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

如果标准布局联合包含两个或多个共享公共初始序列的标准布局结构, 如果标准布局联合对象当前包含其中一个标准布局结构,则允许使用它 检查其中任何一个的公共首字母部分。两个标准布局结构共享一个共同的首字母 如果相应的成员具有布局兼容类型,并且两个成员都不是位字段或 对于一个或多个初始成员的序列,这两个字段都是具有相同宽度的位字段

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);
}

注:本标准未准确定义检验通用初始零件的含义。如果公共初始部分与整个结构一致,这是否意味着可以像我的代码中那样检查整个结构?我想这是语言律师的一个问题。

有人能告诉我在这种情况下如何创建第一个Cat对象吗?-你不能。所以不要写这样的代码。也许这个例子纯粹是为了说明?在合理的代码中,您希望有一个额外的构造函数,是否是默认构造函数。我建议您至少需要使用私有构造函数和函数扩展类,以使基类初始化为一些合理的默认值,如果您不希望基类通常是可构造的。有人能告诉我在这种情况下如何创建第一个Cat对象吗?-你不能。所以不要写这样的代码。也许这个例子纯粹是为了说明?在合理的代码中,您可能希望有一个额外的构造函数,无论它是否是默认构造函数。我建议您至少需要使用一个私有构造函数和一个函数来扩展您的类,以使基类初始化为一些合理的默认值,如果您不希望它通常是可构造的。未定义的行为。πάνταῥεῖ 你能解释一下,在哪种情况下

究竟UB参与了什么?@cwschmidt,例如这里的cat->setnamely;或者在这里cat2.name.UB在几个地方,例如`void setNameconst std::string&name{name_=name;}`和cat->setnamelly;如果UB行为在任何给定版本中都起作用,那就是运气。无法保证将来会继续这样做,因此这是一个坏主意。未定义的行为。πάνταῥεῖ 你能解释一下,UB到底参与了哪种陈述吗?@cwschmidt例如,这里的cat->setnamelly;或者在这里cat2.name.UB在几个地方,例如`void setNameconst std::string&name{name_=name;}`和cat->setnamelly;如果UB行为在任何给定版本中都起作用,那就是运气。不能保证将来会继续这样做,因此这是一个坏主意。请不要这样做。从这个答案至少可以知道UB是什么。请不要。从这个答案至少可以知道UB是什么。这违反了严格的别名规则,即使类具有相同的别名layout@M.M嗯,所以你需要在编译器中禁用严格的别名才能工作。或者用一个工会?是的。您的回答启发我使用union公共初始序列规则编写了一个,尽管我不确信我的答案不是UB,这违反了严格的别名规则,即使类具有相同的别名layout@M.M嗯,所以你需要在编译器中禁用严格的别名才能工作。或者用一个工会?是的。你的回答启发我使用union common initial sequence规则写了一篇文章,尽管我不确信我的答案不是UB alsoI,我想评论一下私有构造函数也常用于实现单例模式。我想评论一下私有构造函数也常用于实现单例模式。