C++ 未建造的混凝土等级

C++ 未建造的混凝土等级,c++,c++17,C++,C++17,我有这样的类层次结构: class Base { public: virtual bool foo() const = 0; static Base& getHeadInstance(); }; class Concrete: public Base { public: explicit Concrete(Base& f): fwd(f) {} // ... other member functions elided bool foo(

我有这样的类层次结构:

class Base {
public:
    virtual bool foo() const = 0;
    static Base& getHeadInstance();
};

class Concrete: public Base {
public:
    explicit Concrete(Base& f): fwd(f) {}

    // ... other member functions elided
    bool foo() const override { return /* some calculation */ && fwd.foo(); }
private:
    Base& fwd;
};
Concrete c1(Base::getHeadInstance());
Concrete c2(c1);
Concrete c3(c2);
enum class ConstructFromBase { Tag };

struct Derived : Base
{
    Derived(Base&, ConstructFromBase);
    Derived(const Derived&) = delete;
};

Derived getDerived();

void test()
{
    Derived d1 = getDerived();
    Derived d2(d1, ConstructFromBase::Tag);
}
这样我就可以构建一系列这样的实例:

class Base {
public:
    virtual bool foo() const = 0;
    static Base& getHeadInstance();
};

class Concrete: public Base {
public:
    explicit Concrete(Base& f): fwd(f) {}

    // ... other member functions elided
    bool foo() const override { return /* some calculation */ && fwd.foo(); }
private:
    Base& fwd;
};
Concrete c1(Base::getHeadInstance());
Concrete c2(c1);
Concrete c3(c2);
enum class ConstructFromBase { Tag };

struct Derived : Base
{
    Derived(Base&, ConstructFromBase);
    Derived(const Derived&) = delete;
};

Derived getDerived();

void test()
{
    Derived d1 = getDerived();
    Derived d2(d1, ConstructFromBase::Tag);
}
因此c3可以做出决策,可能会服从c2,而c2又可以服从c1,类似于责任链模式

问题是c2和c3的构造不正确,它们的fwd成员总是引用Base::getHeadInstance

这里出了什么问题?解决方法是什么

更新:

静态成员返回什么并不重要。假设它返回以下内容的一个实例:

class Head: public Base {
public:
    Head() = default;
private:
    bool foo() const override { return true; }
};
Base& Base::getHeadInstance(){ static Head head; return head; }

您需要删除隐式副本构造函数:

Concrete(const Concrete& c) = delete;
你必须投c1和c2

另一个选项是模板化构造函数:

template<typename T>
Concrete(T& f): fwd(f) {}

您需要删除隐式副本构造函数:

Concrete(const Concrete& c) = delete;
你必须投c1和c2

另一个选项是模板化构造函数:

template<typename T>
Concrete(T& f): fwd(f) {}

您只需要提供自己的副本构造函数,它可以满足您的需要。添加

Concrete(Concrete& c): fwd(c) {}
然后再做

Concrete c1(Base::getHeadInstance());
Concrete c2(c1);
Concrete c3(c2);

让c1.fwd==Base::getHeadInstance、c2.fwd==c1和c3.fwd==c2

您只需要提供自己的复制构造函数,它可以实现您想要的功能。添加

Concrete(Concrete& c): fwd(c) {}
然后再做

Concrete c1(Base::getHeadInstance());
Concrete c2(c1);
Concrete c3(c2);

让c1.fwd==Base::getHeadInstance、c2.fwd==c1和c3.fwd==c2

我以前也遇到过同样的问题。它归结为自定义构造函数DerivedBase&vs隐式定义的复制构造函数Derivedconst派生&;,上的重载解析;,隐式复制构造函数就是赢家。删除它并不能解决这个问题,它仍然参与重载解析,但它确实阻止了错误的事情悄无声息地发生

以下是一个简化的示例:

struct Base
{
    virtual ~Base();
};

struct Derived : Base
{
    Derived(Base&);
    Derived(const Derived&); // Implicitly or explicitly declared in any case.
};

Derived getDerived();

void test()
{
    Derived d1 = getDerived();
    Derived d2(d1); // copies
}
有几种方法可以让代码按照您编写的方式完成您想要的任务。请参阅其他答案,但我想指出的是,您应该特别小心,以避免代码的下一个读者与您之前遇到的相同困惑。转换为Base&,重新调整了复制构造函数语义的用途,或者类似于使用DerivedBase*;都会在未来的读者中提出问题。您可以尝试通过文档来解决这一问题,但很可能有人忽略了这一点并感到困惑。我建议尽可能使意图清晰可见,例如:

class Base {
public:
    virtual bool foo() const = 0;
    static Base& getHeadInstance();
};

class Concrete: public Base {
public:
    explicit Concrete(Base& f): fwd(f) {}

    // ... other member functions elided
    bool foo() const override { return /* some calculation */ && fwd.foo(); }
private:
    Base& fwd;
};
Concrete c1(Base::getHeadInstance());
Concrete c2(c1);
Concrete c3(c2);
enum class ConstructFromBase { Tag };

struct Derived : Base
{
    Derived(Base&, ConstructFromBase);
    Derived(const Derived&) = delete;
};

Derived getDerived();

void test()
{
    Derived d1 = getDerived();
    Derived d2(d1, ConstructFromBase::Tag);
}

这应该非常清楚地传达意图,并且几乎不花费任何费用。有很多其他的方法来编写和命名这样的标签,我的可能不是最规范的…

我以前也遇到过同样的问题。它归结为自定义构造函数DerivedBase&vs隐式定义的复制构造函数Derivedconst派生&;,上的重载解析;,隐式复制构造函数就是赢家。删除它并不能解决这个问题,它仍然参与重载解析,但它确实阻止了错误的事情悄无声息地发生

以下是一个简化的示例:

struct Base
{
    virtual ~Base();
};

struct Derived : Base
{
    Derived(Base&);
    Derived(const Derived&); // Implicitly or explicitly declared in any case.
};

Derived getDerived();

void test()
{
    Derived d1 = getDerived();
    Derived d2(d1); // copies
}
有几种方法可以让代码按照您编写的方式完成您想要的任务。请参阅其他答案,但我想指出的是,您应该特别小心,以避免代码的下一个读者与您之前遇到的相同困惑。转换为Base&,重新调整了复制构造函数语义的用途,或者类似于使用DerivedBase*;都会在未来的读者中提出问题。您可以尝试通过文档来解决这一问题,但很可能有人忽略了这一点并感到困惑。我建议尽可能使意图清晰可见,例如:

class Base {
public:
    virtual bool foo() const = 0;
    static Base& getHeadInstance();
};

class Concrete: public Base {
public:
    explicit Concrete(Base& f): fwd(f) {}

    // ... other member functions elided
    bool foo() const override { return /* some calculation */ && fwd.foo(); }
private:
    Base& fwd;
};
Concrete c1(Base::getHeadInstance());
Concrete c2(c1);
Concrete c3(c2);
enum class ConstructFromBase { Tag };

struct Derived : Base
{
    Derived(Base&, ConstructFromBase);
    Derived(const Derived&) = delete;
};

Derived getDerived();

void test()
{
    Derived d1 = getDerived();
    Derived d2(d1, ConstructFromBase::Tag);
}

这应该非常清楚地传达意图,并且几乎不花费任何费用。有许多其他的方法来编写和命名这样的标签,我的可能不是最规范的…

另一个可能的解决方案是避免使用引用;即使我承认它不是同构的

class Concrete : public Base {
 public:
  explicit Concrete(Base* f) : fwd(*f) {
    // possibly you want to assert precondition? assert(f)?
  }

 private:
  Base& fwd;  // I would prefer Base* here...
};
这样,您就不会冒险调用混凝土的隐式副本构造函数;没有覆盖,你可以删除任何东西

Concrete c1(&Base::getHeadInstance());
Concrete c2(&c1);
Concrete c3(&c2);

另一个可能的解决办法是避免使用引用;即使我承认它不是同构的

class Concrete : public Base {
 public:
  explicit Concrete(Base* f) : fwd(*f) {
    // possibly you want to assert precondition? assert(f)?
  }

 private:
  Base& fwd;  // I would prefer Base* here...
};
这样,您就不会冒险调用混凝土的隐式副本构造函数;没有覆盖,你可以删除任何东西

Concrete c1(&Base::getHeadInstance());
Concrete c2(&c1);
Concrete c3(&c2);

如果提供了getHeadInstance定义,这个问题将更容易回答。此外,它不是虚拟的,因此您将始终调用基本版本。请注意使用引用成员类型。它们通常使赋值难以有意义地实现,移动语义甚至更成问题。当您发现自己在使用引用类型数据成员时,通常最好使用指针。我建议您向基类添加一个虚拟dtor,然后在派生类中的foo上使用override关键字。如果提供了getHeadInstance定义,这个问题将更容易回答。此外,它不是虚拟的,因此您将始终调用基本版本。请注意使用引用成员类型。它们通常使赋值难以有意义地实现,移动语义甚至更成问题。当您发现自己在使用引用类型的数据成员时,您通常会

请不要改用指针。我建议您向基类添加虚拟dtor,然后在派生类中的foo上使用override关键字。我尝试过,编译器总是想使用它,并抱怨它已被删除。删除副本构造函数不会将其从重载解析中删除。关于问题的来源,您是对的,但解决方案不正确。添加强制转换后,您不需要删除复制ctor,除非表明您应该强制转换。是的,模板ctor:template Concrete t&f:fwdf{}请注意,模板构造函数不是副本构造函数,您仍然需要删除副本构造函数才能停止实际的副本。我尝试过这样做,但编译器总是想使用它,并抱怨它已被删除。删除副本构造函数不会将其从重载解析中删除。关于问题的来源,您是对的,但解决方案不正确。添加强制转换后,您不需要删除复制ctor,除非表明您应该强制转换。是的,模板ctor:template Concrete t&f:fwdf{}请注意,模板构造函数不是复制构造函数,您仍然需要删除复制构造函数以停止实际复制。这或多或少是滥用复制构造函数不复制对象。。。我强烈反对这种说法。@MaxLanghof取决于混凝土的意图。它已经不是通常意义上的可赋值或可移动的,所以好的旧时尚值语义已经不可能了。不管怎样,使用它都不是直观的。@FrançoisAndrieux我认为没有值/移动语义比复制构造函数实际上没有复制更为常见和友好。但后者偶尔仍有其用途,因此最终取决于良好的工程判断。这或多或少是滥用复制构造函数不复制对象。。。我强烈反对这种说法。@MaxLanghof取决于混凝土的意图。它已经不是通常意义上的可赋值或可移动的,所以好的旧时尚值语义已经不可能了。不管怎样,使用它都不是直观的。@FrançoisAndrieux我认为没有值/移动语义比复制构造函数实际上没有复制更为常见和友好。但后者偶尔仍有其用途,因此最终取决于良好的工程判断。我将此答案标记为正确答案,因为它解释了编译器选择编写,然后使用自己的副本构造函数而不是我提供的构造函数。在我自己的代码中,我选择删除此处建议的复制构造函数,并使用@Shloim建议的模板构造函数。我将此答案标记为正确答案,因为它说明编译器选择编写并使用自己的复制构造函数,而不是我提供的构造函数。在我自己的代码中,我选择删除此处建议的复制ctor,并使用@Shloim建议的模板ctor。这是我遇到这个问题时使用的选项,这就是我在回答中提到它的原因:D。这是一个折衷方案,在呼叫站点保持它不冗长,并且不需要复杂的知识来理解为什么这是可行的,而确定同构答案会发生什么是困难的。这是我遇到这个问题时使用的选项,这就是我在回答中提到它的原因:D。这是一个折衷方案,在呼叫站点保持它不冗长,并且在确定时不需要复杂的知识来理解它为什么有效对于同构答案会发生什么是困难的。