这是我从构造函数执行的操作:
m_cRedundencyManager->Init(this->RedundencyManagerCallBack);
这无法编译-我收到以下错误:
错误8错误C3867:'CLoggersInfra :: RedundencyManagerCallBack':函数调用缺少参数列表;使用'&CLoggersInfra :: RedundencyManagerCallBack'创建指向成员的指针
我尝试了使用
&CLoggersInfra::RedundencyManagerCallBack
的建议-对我不起作用。 对此有任何建议/解释吗?
我正在使用VS2008。
谢谢!
#1 楼
这是行不通的,因为成员函数指针不能像普通函数指针那样处理,因为它需要一个“ this”对象参数。相反,您可以按如下方式传递静态成员函数,该函数类似于正常的非成员函数:
m_cRedundencyManager->Init(&CLoggersInfra::Callback, this);
函数可以定义如下
static void Callback(int other_arg, void * this_pointer) {
CLoggersInfra * self = static_cast<CLoggersInfra*>(this_pointer);
self->RedundencyManagerCallBack(other_arg);
}
#2 楼
这是一个简单的问题,但答案却非常复杂。简短的答案是您可以使用std::bind1st
或boost::bind
做您想做的事情。下面是更长的答案。编译器建议您使用
&CLoggersInfra::RedundencyManagerCallBack
是正确的。首先,如果RedundencyManagerCallBack
是成员函数,则该函数本身不属于CLoggersInfra
类的任何特定实例。它属于类本身。如果您曾经调用过静态类函数,则可能已经注意到您使用了相同的SomeClass::SomeMemberFunction
语法。由于函数本身属于类而不是特定实例,因此它本身是“静态的”,因此您使用相同的语法。 “&”是必要的,因为从技术上讲您不会直接传递函数-函数不是C ++中的真实对象。相反,从技术上讲,您正在传递函数的内存地址,即,指向函数指令在内存中开始的位置的指针。结果是一样的,您实际上是在“传递一个函数”作为参数。但这只是本例中问题的一半。就像我说的那样,该函数不“属于”任何特定实例。但这听起来像您想将其作为回调传递时考虑到特定的实例。要了解如何执行此操作,您需要了解真正的成员函数:带有额外隐藏参数的常规未定义任何类函数。
例如:
class A {
public:
A() : data(0) {}
void foo(int addToData) { this->data += addToData; }
int data;
};
...
A an_a_object;
an_a_object.foo(5);
A::foo(&an_a_object, 5); // This is the same as the line above!
std::cout << an_a_object.data; // Prints 10!
RedundencyManagerCallBack
需要几个参数?通常我们会说1.。但实际上,foo确实需要2.看看A :: foo的定义,它需要A的特定实例,以使“ this”指针有意义(编译器需要知道什么“这是)。通常,通过语法A::foo
来指定您希望“此”的方式。但这只是将MyObject.MyMemberFunction()
的地址作为第一个参数传递给MyObject
的语法糖。类似地,当我们在类定义中声明成员函数时,我们不会在参数列表中放入“ this”,但这只是语言设计人员为保存类型而送的礼物。相反,您必须指定成员函数为静态,才能选择退出该成员函数,以自动获取额外的“ this”参数。如果C ++编译器将上面的示例转换为C代码(原始的C ++编译器实际上是这样工作的),则可能会编写如下内容:struct A {
int data;
};
void a_init(A* to_init)
{
to_init->data = 0;
}
void a_foo(A* this, int addToData)
{
this->data += addToData;
}
...
A an_a_object;
a_init(0); // Before constructor call was implicit
a_foo(&an_a_object, 5); // Used to be an_a_object.foo(5);
回到您的示例,现在有一个明显的问题。 'Init'想要一个指向带有一个参数的函数的指针。但是
MyMemberFunction
是指向带有两个参数的函数的指针,这两个参数是常规参数和秘密的“ this”参数。这就是为什么仍然出现编译器错误的原因(请注意:如果您曾经使用过Python,这种混淆就是为什么所有成员函数都需要一个“自我”参数的原因)。详细的方法解决这个问题的方法是创建一个特殊的对象,该对象持有指向您想要的实例的指针,并具有一个称为“运行”或“执行”(或重载“()”运算符)的成员函数,该函数采用成员的参数函数,只需在存储的实例上使用这些参数调用成员函数。但这需要您更改“ Init”以使用特殊对象而不是原始函数指针,而且听起来Init是其他人的代码。并且每次出现此问题时都要创建一个特殊的类,这将导致代码膨胀。
因此,现在,终于有了一个不错的解决方案
&CLoggersInfra::RedundencyManagerCallBack
和boost::bind
,每个文档都可以在这里找到:boost :: bind文档,
boost :: function docs
boost::function
将让您接受一个函数,以及该函数的参数,并创建一个新函数,其中该参数被“锁定”到位。因此,如果我有一个将两个整数相加的函数,则可以使用boost::bind
来创建一个新函数,其中一个参数被锁定为5。此新函数将仅使用一个整数参数,并且始终将其专门添加5。使用此技术,您可以将“隐藏”的“ this”参数“锁定”为特定的类实例,并生成一个仅接受一个参数的新函数,就像您想要的一样(请注意,隐藏参数始终是第一个参数,和普通参数按顺序排列)。查看boost::bind
文档中的示例,他们甚至专门讨论了如何将其用于成员函数。从技术上讲,您也可以使用一个称为boost::bind
的标准函数,但是[std::bind1st][3]
更为通用。当然,这里还有一个陷阱。
boost::bind
将为您带来一个不错的boost :: function,但是从技术上讲,这仍然不是像Init可能想要的原始函数指针。幸运的是,boost提供了一种将boost :: function转换为原始指针的方法,如此处StackOverflow所述。它的实现方式超出了此答案的范围,尽管它也很有趣。不用担心这看起来是否太可笑了-您的问题与C ++的多个黑暗角落相交,一旦学习它,
boost::bind
就会非常有用。C ++ 11更新:现在,您可以使用捕获“ this”的lambda函数来代替
boost::bind
。这基本上是让编译器为您生成相同的东西。评论
这是一个很好的答案!
– aardvarkk
13年10月31日,0:45
在答案的开头,建议将std :: bind1st作为实现该解决方案的一种方法,但是答案的后半部分专门针对boost :: bind。如何使用std :: bind1st?
–马布拉汉姆
2014年6月1日,11:05
@mabraham好的,我添加了一个简单的示例,尽管它并不完全适合问题(VS2008):stackoverflow.com/a/45525074/4566599
–罗伊·丹顿(Roi Danton)
17年8月5日在18:18
这应该是公认的答案!如果您无法更改库或传递可选的args,则接受的答案实际上不起作用。
–卡森·麦克尼尔(Carson McNeil)
17年8月11日在23:10
@Joseph Garvin:无法看到std :: bind的答案。这要求参数的类型为std :: function,而不是普通的C函数指针。只是因为您隐藏了传递此内容,这并没有比接受的答案更好。好吧,好的,如果您可以在源代码级访问该函数签名,则可以将foo *更改为std :: function
– Stefan Steiger
19 Mar 27 '19在10:39
#3 楼
此答案是对以上评论的答复,不适用于Visual Studio 2008,但应用于较新的编译器。同时,您不必再使用void指针,并且由于
std::bind
和std::function
可用,因此也无需提升。与void指针相比,优点之一是类型安全,因为使用std::function
明确声明了返回类型和参数:// std::function<return_type(list of argument_type(s))>
void Init(std::function<void(void)> f);
然后可以使用以下方法创建函数指针:
std::bind
并将其传递给Init:auto cLoggersInfraInstance = CLoggersInfra();
auto callback = std::bind(&CLoggersInfra::RedundencyManagerCallBack, cLoggersInfraInstance);
Init(callback);
将
std::bind
与成员,静态成员和非成员函数一起使用的完整示例:#include <functional>
#include <iostream>
#include <string>
class RedundencyManager // incl. Typo ;-)
{
public:
// std::function<return_type(list of argument_type(s))>
std::string Init(std::function<std::string(void)> f)
{
return f();
}
};
class CLoggersInfra
{
private:
std::string member = "Hello from non static member callback!";
public:
static std::string RedundencyManagerCallBack()
{
return "Hello from static member callback!";
}
std::string NonStaticRedundencyManagerCallBack()
{
return member;
}
};
std::string NonMemberCallBack()
{
return "Hello from non member function!";
}
int main()
{
auto instance = RedundencyManager();
auto callback1 = std::bind(&NonMemberCallBack);
std::cout << instance.Init(callback1) << "\n";
// Similar to non member function.
auto callback2 = std::bind(&CLoggersInfra::RedundencyManagerCallBack);
std::cout << instance.Init(callback2) << "\n";
// Class instance is passed to std::bind as second argument.
// (heed that I call the constructor of CLoggersInfra)
auto callback3 = std::bind(&CLoggersInfra::NonStaticRedundencyManagerCallBack,
CLoggersInfra());
std::cout << instance.Init(callback3) << "\n";
}
可能的输出:
Hello from non member function!
Hello from static member callback!
Hello from non static member callback!
此外,使用
std::placeholders
可以动态传递回调的参数(例如,如果f具有字符串参数,则可以在return f("MyString");
中使用Init
)。评论
非常感谢您的回答!我已经花了两个多小时来搜索和尝试不同的方法,但实际上没有任何效果。但这很简单,一分钟后就可以了。
– BadK
18-10-2在12:11
#4 楼
Init
采取什么论点?什么是新的错误消息?C ++中的方法指针很难使用。除了方法指针本身之外,您还需要提供一个实例指针(在您的情况下为
this
)。也许Init
希望将其作为一个单独的参数?#5 楼
指向类成员函数的指针与指向函数的指针不同。类成员接受一个隐式的额外参数(this指针),并使用不同的调用约定。如果您的API需要非成员回调函数,则必须传递给它。
#6 楼
m_cRedundencyManager
能够使用成员函数吗?大多数回调设置为使用常规函数或静态成员函数。请查看C ++ FAQ Lite上的此页面以获取更多信息。更新:您提供的函数声明表明
m_cRedundencyManager
期望使用形式为void yourCallbackFunction(int, void *)
的函数。因此,在这种情况下,成员函数不能用作回调。静态成员函数可能会起作用,但是如果您的情况不可接受,则以下代码也将起作用。请注意,它使用来自void *
的邪恶角色。
// in your CLoggersInfra constructor:
m_cRedundencyManager->Init(myRedundencyManagerCallBackHandler, this);
// in your CLoggersInfra header:
void myRedundencyManagerCallBackHandler(int i, void * CLoggersInfraPtr);
// in your CLoggersInfra source file:
void myRedundencyManagerCallBackHandler(int i, void * CLoggersInfraPtr)
{
((CLoggersInfra *)CLoggersInfraPtr)->RedundencyManagerCallBack(i);
}
#7 楼
死灵法师。我认为到目前为止的答案还不清楚。
举个例子:
假设您有一个像素数组(ARGB int8_t值数组)
// A RGB image
int8_t* pixels = new int8_t[1024*768*4];
现在您要生成一个PNG。
为此,请调用函数toJpeg
bool ok = toJpeg(writeByte, pixels, width, height);
其中writeByte是回调函数
void writeByte(unsigned char oneByte)
{
fputc(oneByte, output);
}
这里的问题:FILE *输出必须是全局变量。
如果您处于多线程环境(例如http服务器)中,则非常糟糕。
因此,您需要某种方法来使输出成为非全局变量,同时保留回调签名。
想到的直接解决方案是闭包,我们可以使用带有成员函数的类进行模拟。
class BadIdea {
private:
FILE* m_stream;
public:
BadIdea(FILE* stream) {
this->m_stream = stream;
}
void writeByte(unsigned char oneByte){
fputc(oneByte, this->m_stream);
}
};
然后做
FILE *fp = fopen(filename, "wb");
BadIdea* foobar = new BadIdea(fp);
bool ok = TooJpeg::writeJpeg(foobar->writeByte, image, width, height);
delete foobar;
fflush(fp);
fclose(fp);
但是,与预期相反,这是行不通的。 />
原因是,C ++成员函数有点像C#扩展函数那样实现。
所以你有
class/struct BadIdea
{
FILE* m_stream;
}
和
static class BadIdeaExtensions
{
public static writeByte(this BadIdea instance, unsigned char oneByte)
{
fputc(oneByte, instance->m_stream);
}
}
所以当你想要的时候要调用writeByte,不仅需要传递writeByte的地址,还需要传递BadIdea-instance的地址。
因此,当您为writeByte过程使用typedef时,它看起来像这样
typedef void (*WRITE_ONE_BYTE)(unsigned char);
并且您有一个writeJpeg签名,看起来像这
bool writeJpeg(WRITE_ONE_BYTE output, uint8_t* pixels, uint32_t
width, uint32_t height))
{ ... }
从根本上讲,不可能将两个地址的成员函数传递给一个地址的函数指针(而无需修改writeJpeg),并且没有解决办法。
在C ++中可以做的第二件事就是使用lambda函数:
FILE *fp = fopen(filename, "wb");
auto lambda = [fp](unsigned char oneByte) { fputc(oneByte, fp); };
bool ok = TooJpeg::writeJpeg(lambda, image, width, height);
但是,因为lambda没做任何事情与将实例传递给隐藏类(例如“ BadIdea”类)不同,您需要修改writeJpeg的签名。
与手动类相比,lambda的优势在于,您只需要更改一个typedef
typedef void (*WRITE_ONE_BYTE)(unsigned char);
到
using WRITE_ONE_BYTE = std::function<void(unsigned char)>;
然后您可以保留其他所有内容。
您还可以使用std :: bind
auto f = std::bind(&BadIdea::writeByte, &foobar);
但是,这在后台仅创建了一个lambda函数,这也需要typedef的更改。
所以不,没有办法将成员函数传递给需要静态函数指针的方法。
但是lambdas是简单的解决方法,只要您拥有对源代码的控制权。
不然,你不走运。
C ++无能为力。
注意:
std :: function需要
#include <functional>
但是,因为C ++允许您将C用作好吧,如果您不介意链接依赖项,则可以使用纯C语言中的libffcall来实现。
从GNU下载libffcall(至少在ubuntu上,不要使用发行版提供的包-它坏了),解压缩。
./configure
make
make install
gcc main.c -l:libffcall.a -o ma
main.c:
#include <callback.h>
// this is the closure function to be allocated
void function (void* data, va_alist alist)
{
int abc = va_arg_int(alist);
printf("data: %08p\n", data); // hex 0x14 = 20
printf("abc: %d\n", abc);
// va_start_type(alist[, return_type]);
// arg = va_arg_type(alist[, arg_type]);
// va_return_type(alist[[, return_type], return_value]);
// va_start_int(alist);
// int r = 666;
// va_return_int(alist, r);
}
int main(int argc, char* argv[])
{
int in1 = 10;
void * data = (void*) 20;
void(*incrementer1)(int abc) = (void(*)()) alloc_callback(&function, data);
// void(*incrementer1)() can have unlimited arguments, e.g. incrementer1(123,456);
// void(*incrementer1)(int abc) starts to throw errors...
incrementer1(123);
// free_callback(callback);
return EXIT_SUCCESS;
}
如果使用CMake,则在add_executable之后添加链接器库
add_library(libffcall STATIC IMPORTED)
set_target_properties(libffcall PROPERTIES
IMPORTED_LOCATION /usr/local/lib/libffcall.a)
target_link_libraries(BitmapLion libffcall)
,或者您可以动态链接libffcall
target_link_libraries(BitmapLion ffcall)
注意:
您可能想要包括libffcall标头和库,或使用libffcall的内容创建cmake项目。
#8 楼
我可以看到init具有以下替代:Init(CALLBACK_FUNC_EX callback_func, void * callback_parm)
其中
CALLBACK_FUNC_EX
是typedef void (*CALLBACK_FUNC_EX)(int, void *);
#9 楼
一个简单的解决方案“解决方法”仍然是创建虚拟函数“接口”类,并在调用程序类中继承它。然后将其作为要调用调用方类的另一个类的参数“可能在构造函数中”传递。DEFINE接口:
class CallBack
{
virtual callMeBack () {};
};
这是您要回叫的类:
class AnotherClass ()
{
public void RegisterMe(CallBack *callback)
{
m_callback = callback;
}
public void DoSomething ()
{
// DO STUFF
// .....
// then call
if (m_callback) m_callback->callMeBack();
}
private CallBack *m_callback = NULL;
};
这是要回叫的类。
class Caller : public CallBack
{
void DoSomthing ()
{
}
void callMeBack()
{
std::cout << "I got your message" << std::endl;
}
};
#10 楼
我认为C ++ FAQ Lite中的问题和答案涵盖了您的问题以及答案中涉及的注意事项。我链接的网页的简短摘要:不要。
因为成员函数没有对象可以调用而没有意义
,您不能直接执行此操作(如果X窗口系统是用C ++重写的,它可能会传递对对象的引用,而不仅仅是对函数的指针;自然,这些对象将体现为
所需的功能,可能还有更多功能。)
评论
现在的链接是isocpp.org/wiki/faq/pointers-to-members#memfnptr-vs-fnptr;看起来他现在说“不要”。这就是仅链接的答案不好的原因。
–lmat-恢复莫妮卡
16年1月12日在19:45
我已经修改了答案@LimitedAtonement。感谢您指出了这一点。您完全正确,只有链接的答案是低质量的答案。但是我们不知道在2008年:-P
–奥诺里奥(Atorio Catenacci)
16年1月13日在16:04
#11 楼
非静态成员函数的指针类型与普通函数的指针类型不同。如果是普通成员或静态成员函数,则类型为
void(*)(int)
。如果是非静态成员函数,则类型为void(CLoggersInfra::*)(int)
。 因此,如果希望使用普通的函数指针,则不能将指针传递给非静态成员函数。
此外,非静态成员函数对对象具有隐式/隐藏参数。目的。
this
指针作为参数隐式传递给成员函数调用。因此,只能通过提供一个对象来调用成员函数。 如果无法更改API
Init
,则可以使用调用该成员的包装函数(普通函数或类静态成员函数)。在最坏的情况下,对象将是包装函数要访问的全局对象。CLoggersInfra* pLoggerInfra;
RedundencyManagerCallBackWrapper(int val)
{
pLoggerInfra->RedundencyManagerCallBack(val);
}
m_cRedundencyManager->Init(RedundencyManagerCallBackWrapper);
如果可以使用API
Init
更改后,有很多选择-对象非静态成员函数指针,函数对象,std::function
或接口函数。 有关C ++工作示例的不同变化,请参见有关回调的文章。
#12 楼
看起来std::mem_fn
(C ++ 11)完全满足您的需求:函数模板std :: mem_fn生成包装器对象,用于指向成员的指针,该指针可以存储,复制和调用对象。指向成员的指针。调用std :: mem_fn时,可以使用对象的引用和指针(包括智能指针)。
评论
尽管这可能是OP的解决方案/解决方法,但我看不出这是如何解决实际问题的。
– Stefan Steiger
19-3-27在10:29
@StefanSteiger答案(解释)在最后一段中(本质上是:“成员函数指针不能像指向自由函数的指针一样进行处理”),其他建议则在我的答案的其他部分。的确,它可能更详尽。但这没关系,这就是为什么我的答案得不到其他答案的原因。有时,更简洁的答案实际上只包含所需的代码,而比更长的答案更好,这就是为什么我的答案被接受的原因。
– Johannes Schaub-小人
19-3-27在10:37
绍布:是的,正是我的意思。但我知道-您应该先写最后一部分,然后说:相反,您可以这样做+(第一部分)
– Stefan Steiger
19-3-27在16:54