【问题标题】:Virtual Inheritance Issues虚拟继承问题
【发布时间】:2014-12-25 18:41:53
【问题描述】:

考虑下面的代码:

#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&lt;T&gt; 无法通过使用 T 的构造函数调用 Thing 的构造函数构造函数。结果,b 类型为 Blob 未正确初始化(所有 Blob 对象都将共享相同的值 3、4、5、20 以及其他值,如字符串和我的特殊类型此处不显示)。那么除了在 Blob 构造函数主体中手动设置这些值(这会破坏Thing(name, 3, 4, 5, 20) 的目的)之外,如何解决这个问题呢?顺便说一句,Thing 是一个抽象类。

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

【问题讨论】:

  • 也许有一个适合Thing 的复制构造函数?
  • 复制构造函数能做什么,而事物的隐式复制构造函数却不能?
  • 没有隐式复制构造函数,只要你开始声明自定义构造函数。

标签: c++ inheritance virtual


【解决方案1】:

看起来您通过调用Thing(name)Clone 中初始化Thing()。一旦初始化一次,构造函数就不会被第二次调用。

我认为虚拟继承并不是您真正想要的。您可能希望创建一个单独的 Thing 对象而不是继承它,而是将其作为成员。

【讨论】:

  • 在保留虚拟继承的同时有没有解决办法?我的程序中已经有这种方式了。
【解决方案2】:

对于virtual 基对象,最派生类是负责​​调用virtual 基的构造函数的类。中间类对virtual 基类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 关键字的任何使用:它很少有用。

【讨论】:

  • 我的Thing 类在我的程序中是抽象的。 Blob 是层次结构中最高的具体类。
  • 然后你需要给它一个合适的辅助对象的构造函数,然后从get_initializer()获得。通常的计算机科学方法适用:您将通过添加另一个间接级别来解决您的问题。
  • 好的,我发布了一个使用间接的解决方案。是否与您所说的“添加另一个间接级别”的意思一致?
【解决方案3】:

好的,我找到了一个可行的解决方案,并添加了 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 的具体类使用不同类型的部分数据(比如{int, int} 仅用于widthmass),那么我上面的解决方案不适用于这个新类,对吧?

添加到问题中:

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) 来定义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
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-01-23
    • 2012-10-11
    • 2016-10-26
    • 2019-12-20
    • 2017-12-09
    • 2013-08-24
    相关资源
    最近更新 更多