【问题标题】:Why is this C++ inheritance code sample behaves like this为什么这个 C++ 继承代码示例的行为是这样的
【发布时间】:2018-04-10 21:10:27
【问题描述】:

我有一个对我来说表现奇怪的代码示例。 通过 C++ 中的继承,可以使用纯虚函数(也称为接口)声明指向基类的指针数组,并在其上调用派生成员函数;

class Base {

public:

    virtual void call() = 0;

};

class Derived1 : public Base {

public:

    void call() override final {

        std::wcout << L"derived 1" << std::endl;

    }

};

class Derived2 : public Base {

public:

    void call() override final {

        std::wcout << L"derived 2" << std::endl;

    }

};

int main() {

    Base* b[2];    

    b[0] = new Derived1;
    b[1] = new Derived2;

    for (int i = 0; i < 2; ++i) {

        b[i]->call();

    }

    return 0;

}

这给出了:

derived 1
derived 2

按计划进行。 但是当我尝试以下代码示例时,它让我有点困惑:

class Base {

public:

    virtual Base* print() = 0;

    template<typename T>
    Base& operator<<(const T &_val) {

        std::wcout << L" d0 << " << _val;
        return *this;

    }

};

class Derived1 : public Base {

public:

    Derived1* print() override final {

        return this;

    }

    template<typename T>
    Derived1& operator<<(const T &_val) {

        std::wcout << L" d1 << " << _val;
        return *this;

    }

};

class Derived2 : public Base {

public:

    Derived2* print() override final {

        return this;

    }

    template<typename T>
    Derived2& operator<<(const T &_val) {

        std::wcout << L" d2 << " << _val;
        return *this;

    }

};

int main() {

    Base* b[2];

    b[0] = new Derived1;
    b[1] = new Derived2;

    for (int i = 0; i < 2; ++i) {

        std::wcout << typeid(*b[i]->print()).name();
        *b[i]->print() << 7 << 7;
        std::wcout << std::endl;

    }

    return 0;

}

输出是:

8Derived1 d0 << 7 d0 << 7
8Derived2 d0 << 7 d0 << 7

这意味着只调用了 Base 的运算符(但 prints() 返回类型似乎是正确的)。

问题是为什么会这样

更新:


似乎我需要没有虚函数的静态多态性。但这怎么可能实现呢? 我需要一组不同的派生类来在 operator


看起来我可以对 operator

【问题讨论】:

  • 你真的认为所有的垂直空格都增加了代码的可读性吗?
  • “问题是它为什么会这样?”因为这就是您对其进行编程的目的。模板化的 operator&lt;&lt; 不是虚拟的。
  • @NeilButterworth 这只是我喜欢的代码风格。没有在规则中找到任何关于这种代码格式的建议。
  • @NeilButterworth 与大多数程序员相比,我更喜欢垂直空白(尽管我承认没有这么多)。不过,它仍然是可读的,除非 SO 为问题和答案制定了所需的编码风格,否则不值得抱怨。
  • @NeilButterworth 缩进都排成一行,垂直空格的使用一致。实际上,我发现不必要地使用宽字符串更让人分心,因为我很少看到这种语法(因为宽字符串不是很便携,你通常应该更喜欢显式的 Unicode 类型)。

标签: c++ inheritance virtual overriding


【解决方案1】:

您的operator &lt;&lt; () 不是虚拟的,因此,如果您调用基类指针的此运算符,则始终调用基类实现。

那是因为它是模板方法,模板方法不能是virtual(见Can a C++ class member function template be virtual?)。

解决方案是为每种支持的数据类型编写虚拟专用 operator &lt;&lt; ()(如果您真的希望它们在继承的类之间有所不同)。

这是修改后的代码:

class Base {

public:

    virtual Base* print() = 0;

    virtual Base& operator<<( int _val ) = 0;

};

class Derived1 : public Base {

public:

    Derived1* print() override final {

        return this;

    }

    Base& operator<<( int _val ) override final {

        std::wcout << L" d1 << " << _val;
        return *this;

    }

};

class Derived2 : public Base {

public:

    Derived2* print() override final {

        return this;

    }

    Base& operator<<( int _val ) override final {

        std::wcout << L" d2 << " << _val;
        return *this;

    }

};

int main() {

    Base* b[2];

    b[0] = new Derived1;
    b[1] = new Derived2;

    for (int i = 0; i < 2; ++i) {

        std::wcout << typeid(*b[i]->print()).name();
        *b[i]->print() << 7 << 7;
        std::wcout << std::endl;

    }

    return 0;

}

另一种方法是,仅通过类层次结构(此处为类名)实现不同部分,并使用模板运算符进行泛型调用。

#include <iostream>
#include <string>

class Base {

public:

    virtual Base* print() = 0;
    virtual const wchar_t* className() const = 0;

    template<typename T>
    Base& operator<<(const T &_val) {

        std::wcout << L" " << className() << L" << " << L" << " << _val;
        return *this;

    }

};

class Derived1 : public Base {

public:
    const wchar_t* className() const override final { return L"d1"; }

    Derived1* print() override final {

        return this;

    }

};

class Derived2 : public Base {

public:
    const wchar_t* className() const override final { return L"d2"; }

    Derived2* print() override final {

        return this;

    }

};

int main() {

    Base* b[2];

    b[0] = new Derived1;
    b[1] = new Derived2;

    for (int i = 0; i < 2; ++i) {

        std::wcout << typeid(*b[i]->print()).name();
        *b[i]->print() << 7 << 7;
        std::wcout << std::endl;

    }

    return 0;

}

最后一种方法是根据类类型进行RTTI转换,并根据类类型调用专门的operator &lt;&lt; ()

#include <iostream>

class Derived1;
class Derived2;

class Base {

public:

    virtual Base* print() = 0;


};

class Derived1 : public Base {

public:

    Derived1* print() override final {

        return this;

    }

    template<typename T>
    Derived1& operator<<(const T &_val) {

        std::wcout << L" d1 << " << _val;
        return *this;

    }

};

class Derived2 : public Base {

public:

    Derived2* print() override final {

        return this;

    }

    template<typename T>
    Derived2& operator<<(const T &_val) {

        std::wcout << L" d2 << " << _val;
        return *this;

    }

};

template<typename T>
Base& operator<<( Base& _base, const T &_val) {

    if( typeid( _base ) == typeid( Derived1 ))
        return dynamic_cast<Derived1*>(&_base)->operator << (_val);
    else
        return dynamic_cast<Derived2*>(&_base)->operator << (_val);
}

int main() {

    Base* b[2];

    b[0] = new Derived1;
    b[1] = new Derived2;

    for (int i = 0; i < 2; ++i) {

        std::wcout << typeid(*b[i]->print()).name();
        *b[i]->print() << 7 << 7;
        std::wcout << std::endl;

    }

    return 0;

}

【讨论】:

  • 是的。如果希望通过模板元编程的魔力使用静态多态性,那么类层次结构就没有必要了。
  • 好像是这样,但是在这个例子中我怎样才能实现静态多态呢?我需要一个派生类数组。
  • 您必须为每种支持的数据类型编写一个专门的实现...或者您必须“混合”模板和类层次结构方法...等等,我将发布另一个示例。
  • 那么你将不得不投射......等等,我会发布最后一个例子,我没有选择;)
  • 说实话,我从未使用过内置 RTTI。通常,当我需要 RTTI 时,我只使用一个简单的虚拟类类型枚举 getter……这很便宜,而且我不需要更多。不过这里有很多关于RTTI的Q&A,随便搜一下。
猜你喜欢
  • 2015-03-12
  • 2021-11-23
  • 2021-07-25
  • 1970-01-01
  • 2011-05-24
  • 1970-01-01
  • 2022-09-27
  • 2013-01-05
  • 1970-01-01
相关资源
最近更新 更多