抽象指向成员函数的指针:安全性和替代方案
Posted
技术标签:
【中文标题】抽象指向成员函数的指针:安全性和替代方案【英文标题】:Abstracting pointer-to-member-function: safety and alternatives 【发布时间】:2014-09-12 19:19:41 【问题描述】:在这个问题中,假设我们已经以一种很好、谨慎的方式处理了所有指针——为了防止问题膨胀,我不想在这里包含我的清理代码!
假设我们有两个类,Foo
和 Bar
,其类定义如下:
class Foo
public:
Foo();
void fooFn();
;
class Bar
public:
Bar();
void barFn();
;
假设Foo
和Bar
必须没有继承关系,但是我们需要同时调用fooFn
和barFn
来响应一些刺激。我们可以创建一个带有容器的控制器类,从该容器中调用 Foo
和 Bar
的特定实例上的 fooFn
和 barFn
- 例如静态 std::vector
- 但我们遇到了一个问题:指向成员的指针Foo
和 Bar
实例的函数类型不同。
通过在控制器类中使用静态vector< std::function<void()>* >
,我们可以解决问题。 Foo
和 Bar
实例可以具有一个函数,该函数通过捕获 this
的 lambda 函数添加指向向量的指针:
void Foo::registerFnPointer()
ControllerClass::function_vector.push_back( new [this]() return this->fooFn(); );
我已经测试过这个方法,它似乎没有任何问题。也就是说,我担心绕过前面提到的类型差异可能导致的问题......我什么都不担心吗?有没有更好的方法来完成等效功能?
【问题讨论】:
这些不是forward declarations。 为什么是vector<std::function<void()>*>
而不是简单的vector<std::function<void()>>
和ControllerClass::function_vector.push_back([this] return fooFn(); );
?
@Conduit 你称之为类声明。
前向声明将是class Foo;
。我猜你可以说你用前向声明成员函数来声明类,但对于函数来说更常见的是声明和定义。
你也可以只使用std::bind
。 see it live.
【参考方案1】:
我看到的唯一问题实际上与函子无关,而是与对象生命周期有关。那就是:我不确定您如何确保在 Foo 或 Bar 实例被销毁时始终取消注册在 ControllerClass 中注册的仿函数。
但是你提到你做了适当的内存管理。
在我看来,您不需要存储指向function<void()>
的指针,您可以简单地将函数存储为值(即具有vector<function<void()>>
)。
在 C++11 和 lambdas 之前,为了达到相同的效果,您也可以使用 (boost) 函数,但您会使用 boost::bind 与 fooFn 的地址和绑定到的第一个参数指向 Foo 对象实例的指针(或引用)。
这将创建一个函数实例,该实例包含调用给定对象上的 fooFn 方法所需的所有信息。然后,您可以将该实例存储在一个向量中以便稍后调用它(并且遇到相同的问题,即确保没有绑定到已销毁对象的 boost::function
保持注册)
编辑:
为了完整起见,特定于绑定成员的 Boost 绑定文档的链接:http://www.boost.org/doc/libs/1_56_0/libs/bind/bind.html#with_member_pointers
您所做的实际上非常相似,只是您现在使用 lambda 来捕获对象指针并定义要调用的函数。
所以我认为你正在做的事情没有问题(除了我已经提到的那个)。
【讨论】:
我没有看到 C++11 之前部分答案的意义。 这个问题被标记为 C++11,但在我看来,这里唯一的 C++11 细节是 std::function 现在是 STL 和 lambda 的一部分。他遇到的问题其实是C++11独立的……我也想说,他现在做的不是以前做不到的。同时要说:旧的做事方式很好(没问题)。新方法(你正在做的事情)实际上几乎相同 -> 并且很好......并且还描述了实现相同目标的另一种方法。 谁知道呢,也许无法切换到支持 C++11 的最新版本编译器的人可能会在某个时间点欣赏这一点... 历史课有助于正确看待其余的答案,imo。谢谢!【参考方案2】:您可以使用适配器类。这对你正在做的事情来说可能有点矫枉过正,但它可能会奏效。
这样做的好处是:
您不必更改原始类。创建void Foo::registerFnPointer()
很丑。
您不必使用静态std::vector
。
您不必处理函数指针。
假设你有两个不同的类:
struct Foo
void fooFn ()
std::cout << "Foo::fooFn ()" "\n" ;
;
struct Bar
void barFn ()
std::cout << "Bar::barFn ()" "\n" ;
;
目标是将它们放入容器中并调用它们各自的*Fn ()
成员函数。
适配器看起来像这样:
struct Adapter_Base
virtual ~Adapter_Base () ;
virtual void adapterFn () = 0 ;
;
template <typename T>
struct Adapter : Adapter_Base
T tVal ;
Adapter (const T &tVal) : tVal (tVal)
void adapterFn () ;
;
template <>
void Adapter <Foo>::adapterFn ()
tVal.fooFn () ;
template <>
void Adapter <Bar>::adapterFn ()
tVal.barFn () ;
你可以这样使用它:
int main ()
std::vector <std::unique_ptr <Adapter_Base> > v1 ;
std::unique_ptr <Adapter_Base> u1 (new Adapter <Foo> (Foo ())) ;
std::unique_ptr <Adapter_Base> u2 (new Adapter <Bar> (Bar ())) ;
v1.push_back (std::move (u1)) ;
v1.push_back (std::move (u2)) ;
for (auto &adapter : v1)
adapter->adapterFn () ;
return 0 ;
【讨论】:
他现在做的还可以。 std::function 实际上正在执行您的适配器所做的事情(它保留要调用的实例和函数)。代码少得多。他实际上是在问他所做的是否可以,是否有更好的方法来完成同样的事情。我不会把你的提议称为更好的解决方案。 @ds27680 在 OP 的帖子中,他正在为每个类添加一个新的成员函数。这样做,您不必更改原始类。您也不必处理使用静态向量或处理函数指针。 但是适配器没有解决新的成员函数“问题”。您只需将他在 registerFnPointer 中编写的代码移动到 main 中。他选择以一种新的课程方法进行注册(即使有争议)与他的问题无关。他本可以编写与您在 main.xml 中编写的相同(我的意思是相同)代码。解决静态向量问题也是如此。您将矢量移动到 main... 这只是对 std::function 的可怕重新实现。以上是关于抽象指向成员函数的指针:安全性和替代方案的主要内容,如果未能解决你的问题,请参考以下文章
C++|详解类成员指针:数据成员指针和成员函数指针及应用场合