虚拟继承问题

时间:2014-12-25 18:41:53

标签: c++ inheritance virtual

请考虑以下代码:

#include <iostream>
#include <string>

struct Thing {
    std::string name;
    int width, length, height, mass;
    Thing (const std::string& n) : name(n) {}
    Thing (const std::string& n, int w, int l, int h, int m) :
        name(n), width(w), length(l), height(h), mass(m) {}
    void print() const {std::cout << name << ", " << width << ", " << length << ", " << height << ", " << mass << '\n';}
    virtual void foo() = 0;  // Abstract class
};

struct Clone : virtual Thing {
    Thing& parent;
    Clone (const std::string& name, Thing& p) : Thing(name), parent(p) {}
};

template <typename T>
struct ClonedType : public Clone, public T {
    ClonedType (const std::string& name, Thing& t) :
        Thing(name), Clone(name, t), T(name) {}
};

// virtual inheritance needed because of ClonedType<Blob> being instantiated:
struct Blob : virtual Thing {
    Blob (const std::string& name) : Thing(name, 3, 4, 5, 20) {}
    virtual void foo() override {}
};

int main() {
    Blob blob("Blob");
    ClonedType<Blob> b("New Blob", blob);
    blob.print();  // Blob, 3, 4, 5, 20
    b.print();  // New Blob, -1, -1, 4237013, 0  // Here's the problem!
}
由于虚拟继承,

Blob的构造函数调用Thing的构造函数很好,但由于虚拟继承ClonedType<T>无法调用Thing的构造函数通过使用T的构造函数。因此,b类型Blob未正确初始化(所有Blob对象将共享相同的值3,4,5,20以及其他值,如字符串和特殊类型我没有在这里展示。那么如何解决这个问题,除了在Blob构造函数体中手动设置这些值(这会破坏Thing(name, 3, 4, 5, 20)的目的)?顺便说一句,事情是一个抽象的阶级。

更新 我在下面添加了一个适用于上述问题的解决方案,但是在该解决方案中,我为这个问题添加了更多复杂性,使新问题得不到解决。

3 个答案:

答案 0 :(得分:0)

看起来您通过调用Thing()Clone内初始化Thing(name)。初始化一次后,构造函数将不再被调用。

我不认为虚拟继承真的是你在寻找的东西。您可能想要创建一个单独的Thing对象而不是继承它,但将其作为成员。

答案 1 :(得分:0)

对于virtual个基础对象,最派生的类是负责调用virtual base的构造函数的类。从中间类调用virtual base Thing只是被忽略了。没有办法解决这个问题。也就是说,问题变成:一些中间类如何安排最派生的类用适当的参数调用Thing

假设您的Thing恰好是具体的,一种可能的方法可能是由某些基类创建临时Thing,例如在Blob中并且具有进一步的派生类抓住它将它传递给Thing构造函数,例如:

template <typename T>
ClonedType<T>::ClonedType(std::string const& name)
    : Thing(T::get_initializer(name))
    , Clone(name)
    , T(name) {
}

当然,这意味着,例如Blob有一个合适的static方法get_initializer()

auto Blob::get_initializer(std::string const& name) -> Thing {
    return Thing(name, 3, 4, 5, 20);
}

虽然我认为这会使初始化工作起作用,但我通常会对设计提出疑问:通常virtual基数已经表明存在问题。如果它基本上是无状态的并且只有一些抽象函数,那么可能是合理的。当然,我对使用virtual关键字非常怀疑:它很少有用。

答案 2 :(得分:0)

好的,我找到了一个有效的解决方案,其中添加的信息Thing是抽象的(从而使解决方案变得更难,上述解决方案无效):

#include <iostream>
#include <string>

struct Thing {
    struct PartialData { int width, length, height, mass; };  //**Added
    struct PartialDataTag {};  //**Added
    std::string name;
    int width, length, height, mass;

    Thing() = default;
    Thing (const std::string& n) : name(n) {}
    Thing (const std::string& n, int w, int l, int h, int m) :
        name(n), width(w), length(l), height(h), mass(m) {}
    Thing (const std::string& n, const PartialData& data) :  // ***Added
        name(n), width(data.width), length(data.length), height(data.height), mass(data.mass) {}
    void print() const {std::cout << name << ", " << width << ", " << length << ", " << height << ", " << mass << '\n';}
protected:
    void setUsingPartialData (const PartialData& data) {  // ***Added
        width = data.width;  length = data.length;  height = data.height;  mass = data.mass;
    }
    virtual void foo() = 0;  // Abstract class
};

struct Clone : virtual Thing {
    Thing& parent;
    Clone (const std::string& name, Thing& p) : Thing(name), parent(p) {}
};

template <typename T>
struct ClonedType : public Clone, public T {
    ClonedType (const std::string& name, Thing& t) :
        Thing(name), Clone(name, t), T(PartialDataTag{}) {}  // ***Changed

};

struct Blob : virtual Thing {
    static const PartialData partialData;
    Blob (const std::string& name) : Thing (name, partialData) {}
    Blob (PartialDataTag) {setUsingPartialData(partialData);}  // ***Added
    virtual void foo() override {}
};
const Thing::PartialData Blob::partialData = {3, 4, 5, 20};  // ***Added

int main() {
    Blob blob("Blob");
    ClonedType<Blob> b("New Blob", blob);
    blob.print();  // Blob, 3, 4, 5, 20
    b.print();  // New Blob, 3, 4, 5, 20
}

有人能想到更好的解决方案吗?许多新添加的东西使它工作,但至少信息3, 4, 5, 20在这里只使用一次。

然而,我可以想到这个解决方案的严重缺点。假设从Thing派生的另一个具体类使用不同类型的部分数据(仅对widthmass说{int,int}),那么我上面的解决方案不会起作用对于这个新课,对吧?

添加到问题:

struct Sample : virtual Thing {
    Sample (const std::string& name, int length, int height) :
        Thing(name, 10, length, height, 50) {}
    virtual void foo() override {}
};

如何实例化

ClonedType<Sample>

并正确初始化?所有Sample个对象的宽度为10,质量为50.哦,我们还假设Thing有更多std :: string数据成员(Sample的静态值也是如此)这样我们就不能使用模板(例如Sample:Data&lt; 10,50&gt;)来定义Sample类。

更新:已解决!

#include <iostream>
#include <string>

struct Thing {
    struct PartialData { std::string codeName;  int width, length, height, mass; };
    struct PartialDataTag {};
    std::string name, codeName;
    int width, length, height, mass;

    Thing() = default;
    Thing (const std::string& n) : name(n) {}
    Thing (const std::string& n, int l, int h) : name(n), length(l), height(h) {}
    Thing (const std::string& n, const std::string& c, int w, int l, int h, int m) :
        name(n), codeName(c), width(w), length(l), height(h), mass(m) {}
    Thing (const std::string& n, const PartialData& data) :
        name(n), codeName(data.codeName), width(data.width), length(data.length), height(data.height), mass(data.mass) {}
    void print() const {std::cout << name << ", " << codeName << ", " << width << ", " << length << ", " << height << ", " << mass << '\n';}
protected:
    void setUsingPartialData (const PartialData& data) {
        codeName = data.codeName;  width = data.width;  length = data.length;  height = data.height;  mass = data.mass;
    }
    virtual void foo() = 0;
};

struct Clone : virtual Thing {
    Thing& parent;
    template <typename... Args>
    Clone (Thing& p, Args&&... args) : Thing (std::forward<Args>(args)...), parent(p) {}
};

template <typename T>
struct ClonedType : public Clone, public T {
    template <typename... Args>
    ClonedType (Thing& t, Args&&... args) : Thing (std::forward<Args>(args)...), Clone(t, std::forward<Args>(args)...), T(PartialDataTag{}) {}
};

struct Blob : virtual Thing {
    static const PartialData partialData;
    Blob (const std::string& name) : Thing (name, partialData) {}
    Blob (PartialDataTag) {setUsingPartialData(partialData);}
    virtual void foo() override {}
};
const Thing::PartialData Blob::partialData = {"Bob", 3, 4, 5, 20};  // !

struct Sample : virtual Thing {
    static const int staticWidth = 10, staticMass = 50;
    Sample (const std::string& name, int length, int height) : Thing(name, "Sam", staticWidth, length, height, staticMass) {}
    Sample (PartialDataTag) {setUsingPartialData(getPartialData());}
    virtual void foo() override {}
    PartialData getPartialData() const {return {"Sam", staticWidth, length, height, staticMass};}  // !
};

int main() {
    Blob blob("Blob");
    ClonedType<Blob> b(blob, "New Blob");
    blob.print();  // Blob, Bob, 3, 4, 5, 20
    b.print();  // New Blob, Bob, 3, 4, 5, 20

    Sample sample("Sample", 11, 12);
    ClonedType<Sample> s(sample, "New Sample", 21, 22);
    sample.print();  // Sample, Sam, 10, 11, 12, 50
    s.print();  // Sample, Sam, 10, 21, 22, 50
}