【问题标题】:How to create the first object if only copy constructor is declared?如果只声明了复制构造函数,如何创建第一个对象?
【发布时间】:2017-10-14 18:57:46
【问题描述】:

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

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

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

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

【问题讨论】:

  • "谁能告诉我在这种情况下如何创建第一个 Cat 对象?" - 你不能。所以不要写那样的代码。
  • 也许这个例子只是为了说明?在合理的代码中,您会希望有一个额外的构造函数,无论它是否是默认构造函数。
  • 我建议你至少需要用一个私有构造函数和一个函数来扩展你的类,如果你不希望它通常是可构造的,那么可以将基类初始化为一些合理的默认值。

标签: c++ copy-constructor default-constructor


【解决方案1】:

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

这里唯一有效的答案是:

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


如果任何其他构造函数被声明为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)
};

【讨论】:

  • 我想说私有构造函数也常用于实现单例模式。
【解决方案2】:

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


现在,尽管如此,并且纯粹是如果手段证明了目的是合理的,但是,有办法使用 layout-compatible 对象创建第一只猫。

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

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


标准布局类是这样的类:

  • 没有非标准布局类(或此类类型的数组)或引用类型的非静态数据成员,
  • 没有虚函数 (10.3) 和虚基类 (10.1),
  • 对所有非静态数据成员具有相同的访问控制(第 11 条),
  • 没有非标准布局基类,
  • 要么在派生最多的类中没有非静态数据成员,并且最多有一个具有非静态数据成员的基类,要么没有具有非静态数据成员的基类,并且 *没有与第一个非静态数据成员相同类型的基类。

标准布局结构是使用类键struct 或类键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 成员用于演示(布局兼容)成员的复制。

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

【讨论】:

  • 这违反了严格的别名规则(即使类具有相同的布局)
  • @M.M 嗯,因此您需要在编译器中禁用严格别名才能使其正常工作。或者也许以某种方式使用联合?
  • 是的。您的回答启发了我使用联合通用初始序列规则编写一个,尽管我不相信我的答案也不是 UB
【解决方案3】:

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

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

【讨论】:

    【解决方案4】:

    您可以这样做,尽管它完全不推荐;执行下面显示的操作是非常非常糟糕的做法,会导致“未定义的行为”

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

    如果您尝试做这样的事情,那么您可能以完全错误的方式解决问题,您应该重新考虑您的解决方案。 p>

    【讨论】:

    • 请不要。从这个答案至少可以了解什么是 UB。
    【解决方案5】:

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

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

    为了让“猫”栩栩如生,您可以进行一些重新诠释来创建您的“第一只”猫:

    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,方法是按照 cmets 中的说明进行扩展,例如:

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

    【讨论】:

    • 未定义的行为。
    • πάντα ῥεῖ 你能解释一下,UB 到底在哪一个陈述中涉及?
    • @cwschmidt 例如这里cat-&gt;setName("Lilly"); 或这里cat2.name()
    • 好几个地方都有UB,例如` void setName(const std::string& name) { name_ = name; }` 和cat-&gt;setName("Lilly");
    • 如果 UB 行为恰好在任何给定版本中起作用,那是幸运的。不能保证将来会继续这样做,因此是个坏主意。
    猜你喜欢
    • 2018-11-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-11-05
    • 1970-01-01
    • 2021-06-04
    • 1970-01-01
    相关资源
    最近更新 更多