سؤال

أنا أستخدم واجهة برمجة التطبيقات (API) التي تتطلب مني تمرير مؤشر دالة كرد اتصال.أحاول استخدام واجهة برمجة التطبيقات هذه من صفي ولكني أتلقى أخطاء في الترجمة.

هذا ما فعلته من المنشئ الخاص بي:

m_cRedundencyManager->Init(this->RedundencyManagerCallBack);

هذا لا يتم تجميعه - أحصل على الخطأ التالي:

الخطأ 8 الخطأ C3867:'CLoggersInfra::RedundencyManagerCallBack':استدعاء دالة قائمة الوسائط المفقودة؛استخدم "&CLoggersInfra::RedundencyManagerCallBack" لإنشاء مؤشر للعضو

لقد حاولت استخدام الاقتراح &CLoggersInfra::RedundencyManagerCallBack - لم ينجح معي.

أي اقتراحات/تفسير لهذا؟؟

أنا أستخدم VS2008

شكرًا!!

هل كانت مفيدة؟

المحلول

وهذا لا يعمل بسبب مؤشر دالة عضو لا يمكن التعامل معها مثل مؤشر وظيفة عادية، لأنه يتوقع "هذه" الحجة الكائن.

وبدلا من ذلك يمكنك تمرير دالة عضو ثابتة على النحو التالي، التي تشبه وظائف غير الأعضاء العادية في هذا الصدد:

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);
}

نصائح أخرى

وهذا هو سؤال بسيط ولكن الجواب معقد بشكل مدهش. الجواب القصير هو أنك يمكن أن تفعل ما نحاول القيام به مع الأمراض المنقولة جنسيا :: bind1st أو دفعة :: مأزق. الجواب أطول أدناه.

والمترجم هو الصحيح أن أقترح عليك استخدام وCLoggersInfra :: RedundencyManagerCallBack. أولا، إذا RedundencyManagerCallBack هي وظيفة الأعضاء، وظيفة في حد ذاته لا تنتمي إلى أي مثيل معين من CLoggersInfra الصف. وهو ينتمي إلى الفئة نفسها. إذا كنت قد دعا من أي وقت مضى وظيفة فئة ثابتة من قبل، كنت قد لاحظت استخدام نفس بناء الجملة SomeClass :: SomeMemberFunction. لأن وظيفة في حد ذاته هو "ثابت" بمعنى أنه ينتمي إلى فئة بدلا من حالة معينة، يمكنك استخدام نفس بناء الجملة. و'&' ضروري لأن من الناحية الفنية كنت لم تمر ظائف مباشرة - وظائف ليست كائنات حقيقية في C ++. بدلا من ذلك كنت تمر تقنيا عنوان الذاكرة عن وظيفة، وهذا هو، مؤشر إلى حيث تبدأ تعليمات وظيفة في الذاكرة. والنتيجة هي نفسها على الرغم من أنك فعال 'تمرير الدالة' كمعلمة.

ولكن هذا فقط نصف المشكلة في هذه الحالة. كما قلت، RedundencyManagerCallBack وظيفة لا "تنتمي" إلى أي مثيل معين. ولكن يبدو وكأنك تريد أن يمر على أنها رد مع حالة معينة في الاعتبار. لفهم كيفية القيام بذلك تحتاج إلى فهم ما وظائف الأعضاء هي في الواقع: وظائف العادية يست محددة في أي من الدرجة مع معلمة خفية اضافية

وعلى سبيل المثال:

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!

كم عدد المعلمات التي تستغرقها A :: فو؟ وعادة ما أن أقول 1. ولكن تحت غطاء محرك السيارة، فو يأخذ حقا 2. بالنظر إلى تعريف A :: فو، فإنه يحتاج نسخة معينة من A من أجل أن "هذا" مؤشر لتكون ذات مغزى (المترجم يحتاج إلى معرفة ما هذا هو). الطريقة التي عادة ما تحدد ما تريد 'هذا' لتكون هي من خلال MyObject.MyMemberFunction جملة (). ولكن هذا هو السكر فقط النحوي لتمرير عنوان MyObject كمعلمة الأول إلى MyMemberFunction. وبالمثل عندما نعلن ظائف الأعضاء داخل تعريفات فئة لم نضع 'هذا' في قائمة المعلمة، ولكن هذا هو مجرد هدية من المصممين اللغة لإنقاذ الكتابة. بدلا من ذلك لديك لتحديد أن دالة عضو غير ثابتة الانسحاب منه تلقائيا الحصول على اضافية 'هذا' المعلمة. إذا كان مترجم 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);

وبالعودة إلى المثال الخاص بك، والآن هناك مشكلة واضحة. "التهيئة" يريد مؤشر دالة التي تأخذ معلمة واحدة. ولكن وCLoggersInfra :: RedundencyManagerCallBack هو مؤشر دالة التي تأخذ معلمتين، انها المعلمة العادية والسرية 'هذا' المعلمة. وبالتالي لماذا كنت لا يزال الحصول على خطأ مترجم. (كملاحظة جانبية: إذا كنت قد استخدمت من أي وقت مضى بيثون، وهذا النوع من الارتباك هو لماذا مطلوب معلمة "الذات" لجميع وظائف الأعضاء)

والطريقة مطول للتعامل مع ذلك هي إنشاء كائن خاص الذي يحمل مؤشر إلى مثيل تريد ولها وظيفة عضو يسمى شيء من هذا القبيل 'المدى' أو 'تنفيذ' (أو الزائدة و'()' المشغل) أن يأخذ المعلمات من أجل وظيفة الأعضاء، وببساطة يدعو الدالة العضو مع تلك المعايير على سبيل المثال المخزنة. ولكن هذا يتطلب منك تغيير "التهيئة" لاتخاذ كائن خاص بك بدلا من مؤشر الدالة الخام، وهذا يبدو وكأنه التهيئة هو رمز لشخص آخر. وجعل فئة خاصة للفي كل مرة هذه المشكلة تأتي سيؤدي إلى سخام التعليمات البرمجية.

وحتى الآن، وأخيرا حل جيد، وتعزيز :: ربط وتعزيز :: وظيفة، والوثائق لكل يمكنك أن تجد هنا:

تعزيز :: مستندات ربط ، <وأ href = "http://www.boost.org/doc/libs/1_37_0/doc/html/function/tutorial.html" يختلط = "لاالمرجع "> دفعة :: مستندات ظيفة

ودفعة :: ربط سوف تمكنك من اتخاذ وظيفة، ومعلمة لتلك الوظيفة، وجعل وظيفة جديدة حيث أن المعلمة "مؤمن" في المكان. حتى لو كان لدي وظيفة يضيف عددين، يمكنني استخدام دفعة :: ربط لجعل وظيفة جديدة حيث إحدى المعلمات مقفل القول 5. هذه الوظيفة الجديدة سوف يستغرق سوى المعلمة عدد صحيح واحد، وسوف تضيف دائما 5 على وجه التحديد لذلك. باستخدام هذه التقنية، يمكنك 'جني' الخفية 'هذا' المعلمة أن يكون مثيل فئة معينة، وتوليد الوظائف الجديدة التي يستغرق سوى معلمة واحدة، تماما كما كنت تريد (لاحظ أن المعلمة خفية هي دائما <م> أولا المعلمة، والمعايير العادية تأتي في الترتيب بعد ذلك). نظرة على مستندات دفعة :: ربط للحصول على أمثلة، يناقشون حتى على وجه التحديد استخدامه لوظائف الأعضاء. من الناحية الفنية هناك دالة قياسية تسمى الأمراض المنقولة جنسيا :: bind1st التي يمكن استخدامها أيضا، ولكن تعزيز :: مأزق هو أعم.

وبطبيعة الحال، هناك واحد فقط أكثر الصيد. تعزيز :: سوف ربط جعل دفعة لطيفة :: وظيفة لك، ولكن هذا لا يزال من الناحية الفنية لا مؤشر دالة الخام مثل يريد التهيئة على الأرجح. الحمد لله، وزيادة يوفر وسيلة لتحويل دفعة :: الدالة على المؤشرات الأولية، كما هو موثق في ستاكوفيرفلوو <لأ href = "https://stackoverflow.com/questions/282372/demote-boostfunction-to-a-plain-function-pointer "> هنا . كيف تطبق هذا هو خارج نطاق هذه الإجابة، على الرغم من انها مثيرة للاهتمام للغاية.

لا تقلق إذا كان هذا يبدو ساذجا الصعب - سؤالك يتقاطع العديد من C ++ الصورة الزوايا المظلمة، وتعزيز :: ربط مفيد بشكل لا يصدق بمجرد معرفة ذلك

.

وC ++ 11 التحديث: بدلا من دفعة :: ربط يمكنك الآن استخدام دالة امدا الذي يلتقط 'هذا'. هذا هو في الأساس وجود مترجم توليد نفس الشيء بالنسبة لك.

وهذا الجواب هو رد على تعليق أعلاه ولا يعمل مع VisualStudio 2008 ولكن ينبغي أن يفضل مع المزيد من المجمعين الأخيرة.


وفي الوقت نفسه لم يكن لديك لاستخدام مؤشر الفراغ بعد الآن، وهناك أيضا حاجة لدفعة منذ <لأ href = "http://en.cppreference.com/w/cpp/utility/functional/bind" يختلط تتوفر = "noreferrer"> std::bind و std::function . واحدة ميزة (بالمقارنة مع مؤشرات باطلة) هي اكتب سلامة منذ نوع الإرجاع وذكر الحجج صراحة باستخدام std::function:

// std::function<return_type(list of argument_type(s))>
void Init(std::function<void(void)> f);

وبعد ذلك يمكنك إنشاء مؤشر الدالة مع std::bind وتمريرها إلى التهيئة:

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 الذي يمكن تمرير الوسائط حيوي إلى الاستدعاء ( على سبيل المثال هذا يتيح استخدام return f("MyString"); في Init إذا و لديها معلمة سلسلة).

وماذا يفعل حجة Init تأخذ؟ ما هي رسالة الخطأ الجديدة؟

هي من الصعب بعض الشيء لاستخدام

ومؤشرات الطريقة في C ++. وبالاضافة الى طريقة المؤشر نفسه، تحتاج أيضا إلى توفير مؤشر المثيل (في حال this الخاص بك). ربما Init تتوقع أنها حجة منفصلة؟

ومؤشر إلى وظيفة عضو الفئة ليست هي نفسها كما مؤشر إلى وظيفة. يأخذ أحد أعضاء الطبقة حجة اضافية الضمني (و<م> هذه المؤشر)، ويستخدم اصطلاح استدعاء مختلفة.

إذا API الخاص يتوقع دالة رد غير عضو، وهذا ما لديك لتمرير إليها.

يكون m_cRedundencyManager قادرة على استخدام وظائف الأعضاء؟يتم إعداد معظم عمليات الاسترجاعات لاستخدام الوظائف العادية أو وظائف الأعضاء الثابتة.نلقي نظرة على هذه الصفحة في الأسئلة الشائعة حول C++ 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);
}

وأستطيع أن أرى أن الحرف الأول له في التجاوز التالي:

Init(CALLBACK_FUNC_EX callback_func, void * callback_parm)

وحيث CALLBACK_FUNC_EX هو

typedef void (*CALLBACK_FUNC_EX)(int, void *);

والإجابة من < وأ href = "https://isocpp.org/wiki/faq" يختلط = "نوفولو noreferrer"> C ++ التعليمات لايت يغطي سؤالك والاعتبارات المشاركة في الإجابة بشكل جيد جدا على ما أعتقد. مقتطف صغير من صفحة الويب I المرتبط:

<اقتباس فقرة>   

لا.

     

ولأن وظيفة عضو لا معنى له دون كائن لاستدعاء   على، لا يمكنك أن تفعل ذلك مباشرة (إذا كان نظام نوافذ X   إعادة كتابة في C ++، فمن المحتمل تمرير ما يشير إلى الأشياء المحيطة،   ليس فقط مؤشرات إلى وظائف. من الطبيعي أن الأشياء سوف تجسد   الوظيفة المطلوبة، وربما في مجموعها أكثر بكثير).

استحضار الأرواح.
أعتقد أن الإجابات حتى الآن غير واضحة بعض الشيء.

دعونا نجعل مثالا:

لنفترض أن لديك مجموعة من وحدات البكسل (مجموعة من قيم ARGB int8_t)

// A RGB image
int8_t* pixels = new int8_t[1024*768*4];

الآن تريد إنشاء PNG.للقيام بذلك، يمكنك استدعاء الدالة إلىJpeg

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.

لذا، عندما يكون لديك typedef لإجراء writeByte، يبدو هكذا

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);

ولكن هذا، خلف الكواليس، يقوم فقط بإنشاء دالة لامدا، والتي تحتاج بعد ذلك أيضًا إلى التغيير في typedef.

لذلك لا، لا توجد طريقة لتمرير وظيفة عضو إلى طريقة تتطلب مؤشر وظيفة ثابت.

لكن لامدا هي الطريقة السهلة، بشرط أن يكون لديك السيطرة على المصدر.
وإلا فلن يحالفك الحظ.
لا يوجد شيء يمكنك فعله باستخدام C++.

ملحوظة:
الأمراض المنقولة جنسيا::تتطلب الوظيفة #include <functional>

ومع ذلك، نظرًا لأن لغة C++ تسمح لك باستخدام لغة C أيضًا، فيمكنك القيام بذلك باستخدامها libffcall في عادي C، إذا كنت لا تمانع في ربط التبعية.

قم بتنزيل libffcall من GNU (على الأقل على نظام ubuntu، لا تستخدم الحزمة المقدمة من التوزيعة - فهي معطلة)، قم بفك الضغط.

./configure
make
make install

gcc main.c -l:libffcall.a -o ma

ج الرئيسية:

#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، أو إنشاء مشروع cmake بمحتويات libffcall.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top