【问题标题】:C++ Programm calls superclass method instead of subclass method in factory patternC++程序调用超类方法而不是工厂模式中的子类方法
【发布时间】:2013-08-16 12:29:07
【问题描述】:

我想像这样为我的 Source-Plugins 建立一个工厂:

class PluginFactory {
public:
    PluginFactory(){};
    virtual ~PluginFactory(){};

    static MySource* getSourceById(int id, ParameterList& pList){
        switch (id){
            case 1:
                return new StringSource(pList);
            default:
                std::cout << "Unknown PluginId!" << std::endl;
                return nullptr;
        }

    }
};

MySource 在模式中不能像往常一样抽象,因为它稍后会在模板类中使用。

当我调用返回的MySource* 的方法时,我得到的是超类MySource 的方法,而不是子类StringSource 的重写方法。

任何想法如何解决这个问题?

编辑:

我将超类方法声明为虚拟:

MySource{
    ...
    virtual std::streamsize read(char* s, std::streamsize n){
    ...
    }
};

我在子类的 read-Method 中添加了 override 命令:

class StringSource: public MySource {
    ...
std::streamsize read(char* s, std::streamsize n) override
{
    ...
}

};

但它仍然使用超类方法。一定还有别的原因……

顺便说一句。我将 Source-Class 放入 boost::iostream::filtering_istream 中,如下所示:

MySource* source = PluginFactory::getSourceById(1, pluginList[0].second);
boost::iostreams::filtering_istream in;
in.push(*source);

所以我自己不调用 read-method。

【问题讨论】:

  • 请向我们展示您实际返回源的代码,并尝试调用它的方法。
  • 我自己不调用该方法。我将 Source-Class 推入 boost::iostream::filtering_istream 并在那里调用 read-Method。请参阅上面的编辑。

标签: c++ inheritance factory-pattern


【解决方案1】:

问题是这样的:

in.push(*source);

根据documentation,这将复制参数。由于您的基类是可复制的而不是抽象的,因此您会遇到切片问题,其中仅复制基本子对象。

您应该能够通过传递引用包装器来修复它:

in.push(std::ref(*source));

我建议您将基类设为抽象(或至少不可复制),以防止切片的可能性。我不明白你为什么不把它抽象化;但是任何要求它具体的东西听起来很可怕而且容易出错。

更新:由于您将其具体化只是为了将其传递给此函数,因此您应该再次将其抽象化,并改为传递引用包装器。

【讨论】:

  • 这很可怕... in.push() 需要一个具体的类,因为它将在模板中使用,而模板不接受抽象类。有没有办法将抽象类插入in-push()?
  • @schindi:是的。按照我的回答和链接文档中的建议,使用参考包装器。
  • 你的意思是如果我使用引用包装器我可以使用抽象类?顺便提一句。尽管我包含了 .,但我收到一个错误,即 ref 不是 std 的成员
  • @schindi:是的。引用包装器是一个小的、可复制的对象,其中包含对您的对象的引用,可以像实际引用一样使用它。因此,流将获取它的副本,并使用它调用对象上的函数(使用正确的类型),而不会尝试复制它。
  • @schindi:“ref 不是 std 的成员”——这很奇怪。也许您的库实现还不支持它,在这种情况下,您可以改用 boost::ref
【解决方案2】:

当我调用返回的 MySource* 的方法时,我得到的是超类 MySource 的方法,而不是子类 StringSource 的重写方法。

我不确定这是否可行(没有看到任何相关代码),但听起来您需要将超类方法(您调用的方法)声明为virtual,以便编译器知道运行被覆盖的方法版本(在本例中为 StringSource),而不是超类版本。

希望这会有所帮助!

【讨论】:

  • @TemplateRex 那也是 :D 但真的,想不出超类方法运行的任何其他原因,所以也许我是对的
  • 该方法从一开始就被声明为虚拟的。请参阅上面的编辑。
【解决方案3】:

您调用的MySource 中的方法必须是virtual,否则派生类中的方法不会覆盖,而是隐藏它。例如,

class Base {
public:
            void foo() const { std::cout << "Base::foo()\n"; }
    virtual void bar() const { std::cout << "Base::bar()\n"; }
};

class Derived : public Base {
public:
    void foo() const { std::cout << "Derived::foo()\n"; } // hides Base::foo
    void bar() const { std::cout << "Derived::bar()\n"; } // overrides Base::bar
};

Derived d;
Base& b = d;

d.foo(); // outputs Derived::foo()
b.foo(); // outputs Base::foo()
b.bar(); // outputs Derived::bar()

如果你使用的是 C++11,我建议在 Derived::bar() 的声明中使用 override 关键字:

void bar() const override { std::cout << "Derived::bar()"; }

更准确地说,您应该在所有旨在覆盖基类中的派生方法的声明中使用override。如果你犯了一个错误并且派生类中的方法没有覆盖基类中的任何方法,那么编译器将引发错误。

Herb Sutter 解释了这些问题 here

更新:在 OP 添加更多信息之后。

此问题的另一个可能原因如下。如果基类被复制(例如,当它通过值传递给函数时),那么复制会丢失有关动态类型的信息。例如,重新考虑上面的示例和这些函数:

void call_bar_pass_by_value(Base x) {
  x.bar(); 
}

void call_bar_pass_by_reference(const Base& x) {
  x.bar();
}

然后,使用b 调用它们会得到:

call_bar_pass_by_value(b);     // outputs Base::bar()
call_bar_pass_by_reference(b); // outputs Derived::bar()

我不熟悉 Boost.Iostreams,但查看 filtering_stream::push() here 的参考文档,我们可以看到该函数确实通过引用来获取其参数。因此,我刚才描述的问题不会在这里发生。但是,此函数可能会调用另一个调用另一个函数的函数......其中一个可能会按值获取参数(或制作它的副本)。

OP 声明“MySource 在模式中不能像往常一样抽象,因为它稍后将在模板类中使用”。这表明尝试复制该对象。

我现在不知道该建议什么,只是为了测试我上面的理论(它不能解决问题)我会暂时使MySource 的复制构造函数protected 看看 Boost.Iostreams 是否尝试复制MySource。如果是这样,那么代码将无法编译。

【讨论】:

  • 我在上面添加了一些代码...我的方法被声明为虚拟的并且我添加了覆盖命令。到目前为止什么都没改变...
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-03-22
相关资源
最近更新 更多