如何在派生类构造函数后强制调用基类函数?

5
我正在寻找一个干净的C++惯用语来处理以下情况:
class SomeLibraryClass {
  public:
    SomeLibraryClass() { /* start initialization */ }
    void addFoo() { /* we are a collection of foos */ }
    void funcToCallAfterAllAddFoos() { /* Making sure this is called is the issue */ }
};
class SomeUserClass : public SomeLibraryClass {
  public:
    SomeUserClass() {
      addFoo();
      addFoo();
      addFoo(); // SomeUserClass has three foos.
    }
};
class SomeUserDerrivedClass : public SomeUserClass {
  public:
    SomeUserDerrivedClass() {
      addFoo(); // This one has four foos.
    }
};

因此,我真正想要的是让SomeLibraryClass在构建过程结束时强制调用funcToCallAfterAllAddFoos。用户不能将其放在SomeUserClass :: SomeUserClass()的末尾,否则会破坏SomeUserDerrivedClass。如果他将其放在SomeUserDerrivedClass的末尾,则永远不会为SomeUserClass调用它。
为了进一步说明我的需求,请想象/* start initialization */获取锁,并且funcToCallAfterAllAddFoos()释放锁。
编译器知道对象的所有初始化何时完成,但是我能通过某些好的技巧获得这些信息吗?
5个回答

11

我可能会使用某种工厂来实现这个功能。下面的代码应该被理解为伪代码,我还没有尝试编译它或者其他什么。

class LibraryClass
{
public:
   template<typename D>
   static D *GetNewInstance()
   {
      // by assigning the new D to a LibraryClass pointer, you guarantee it derives from LibraryClass at compile time
      // that way, the user can't accidentally type "LibraryClass::GetNewInstance<int>()" and have it work
      LibraryClass *c = new D();
      c->funcToCallAfterAllAddFoos();
      return c;
   }

   ...
};

+1. 这是唯一一个允许“SomeUserDerrivedClass”模式有效工作(派生类继承父类的Foo)的答案。 - Billy ONeal
2
不,任何构造函数传递的方法都可以工作,你只需要在从派生类传递的集合上进行构建。 - Stephen
然而,所有派生类的构造函数都必须是public,所以可能会意外绕过工厂。有什么解决办法吗? - Thomas
2
@Thomas:我不明白将构造函数设为公共的要求。工厂可以(而且可能应该)是一个友元类/函数。请注意,这不会破坏封装性也不会增加耦合:工厂和创建的对象已经紧密耦合,并且您仍然控制工厂方法,因此您拥有与实现类方法相同的控制权。 - David Rodríguez - dribeas
实现中在返回语句中缺少一个 static_cast 回到 D。可以使用模板元编程执行相同的检查,或者直接跳过,因为它不会与 int 编译:funcToCallAfterAllAddFoos 不会编译...而且使用该方法名称的类匹配模板要求的层次结构之外的机会很小 :P - David Rodríguez - dribeas
@dribeas:将工厂设置为“友元”可以算作是“绕过这个问题的方法”,谢谢 :) 当然,派生类仍然可能会意外地声明一个“公共”构造函数,但是无法防止这种情况的发生。 - Thomas

6

我不确定这是否可行。但是,您可以稍微重新设计一下:给您的基类构造函数一个参数std::vector<Foo> const &foosToBeAdded,并让派生类传递正确的foo

class SomeLibraryClass {
  public:
    SomeLibraryClass(std::vector<Foo> const &foosToBeAdded) {
      /* start initialization */
      std::for_each(foosToBeAdded.begin(), foosToBeAdded.end(),
                    std::bind1st(std::mem_fun(&SomeLibraryClass::addFoo), this));
      funcToCallAfterAllAddFoos();
    }
  private:
    void addFoo(Foo const &someFoo) { /* we are a collection of foos */ }
    void funcToCallAfterAllAddFoos() { /* this is now called at the right time */ }
};

class SomeUserClass : public SomeLibraryClass {
  public:
    SomeUserClass() :
      SomeLibraryClass(makeAFooVector())
    {
    }
  private:
    std::vector<Foo> makeAFooVector() { /* return a vector with three Foos */ }
};

通过让SomeUserClass构造函数接收vectorFoo,可以扩展该模式。然后在调用基类构造函数之前,它会将自己的Foo添加到列表中。

您也可以传递迭代器而不是vector。留作练习。


小问题:应该使用std::for_each而不是显式的for循环。+1 - Billy ONeal
谢谢,已编辑。我知道那个,但如果不先尝试编译它,我就不会弄对了 ;) - Thomas
坦白地说,我认为for比绑定更明确...我正在等待lambda表达式!无论如何,这显然是更好的解决方案,没有变通方法,也没有意外创建行为不当的派生类的风险。 - Matthieu M.

1

尝试使用非虚拟接口习惯用法。将公共方法设置为非虚拟的,但是让它调用一个私有虚拟方法。派生类重写此私有虚拟方法(是的,派生类可以重写私有虚拟方法)。在公共非虚拟方法中放置锁定内容,围绕对私有虚拟方法的调用。

编辑: 仔细查看代码后,我认为您最好在基类中有一个构造函数,该构造函数接受Foo对象的容器并将它们添加到其中。


那怎么能解决问题呢?当构造完成时,仍然有人必须调用虚方法。 - moswald
不,虚方法的唯一调用者是非虚方法。派生类不需要调用任何东西。 - Fred Larson
但是这样虚方法会被基类构造函数(间接地)调用,而基类构造函数是第一个运行的,而不是最后一个... - Thomas
唉,我没有仔细查看代码,没看到这是从构造函数中调用的。 - Fred Larson
@Mike Elkins:是的,我讨厌这种情况。但也许我的编辑更接近标准了? - Fred Larson

1
为什么要提供一个公共的addfoo方法呢?你说这都是初始化,那就让集合在构造函数中传递吧。
然后你可以从构造函数中调用非虚拟functocall函数。

0

由于C++不允许反射,因此您无法直接获取此信息。(尽管可能有一些我不知道的方法可以防止编译成功)

但是,我对这里的设计表示质疑。如果SomeLibraryClass完成后释放锁定是否更有意义?如果您担心多次调用AddFoo的效率,您的库可以提供一个接受std::vector<Foo>的成员,它只需要获取和释放一次锁。


需要澄清的是,在添加期间必须保持锁定状态,直到对象准备就绪才能释放。在每次 addFoo 后释放锁定不仅效率低下,而且是错误的。 - Mike Elkins
@Mike Elkins:我并不是说在每个AddFoo之后都要发布它。我的意思是,一个简单的解决方案是传递一个vector<Foo>,这样可以通过单个函数调用添加所有的Foos。然后添加Foos的函数可以自行释放锁定。 - Billy ONeal
关于采用向量的方法,可能会起作用,但是如何使创建SomeUserDerrivedSubclass变得容易呢?在从SomeUserClass调用到SomeUserDerrivedSubclass结束之前,我必须通过锁定来保持它。 - Mike Elkins
@Mike Elkins:不是这样的。但是,如果是这种情况,强制派生构造函数调用您的方法也不起作用,因为SomeUserClass完成构造并在SomeUserDerivedClass完成构造之前调用您的方法。SomeUserClass没有神奇的方式知道它正在构造一个派生类。 - Billy ONeal

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接