【问题标题】:Member-Function Pointers With Default Arguments具有默认参数的成员函数指针
【发布时间】:2010-02-08 22:22:42
【问题描述】:

我正在尝试创建一个指向具有默认参数的成员函数的指针。当我通过这个函数指针调用时,我不想为默认参数指定一个参数。根据标准,这是不允许的,但我以前从未发现任何标准不允许我无法以其他符合的方式做的事情。到目前为止,我还没有找到这样做的方法。

这是说明我要解决的问题的代码:

class MyObj
{
public:
 int foo(const char* val) { return 1; }
 int bar(int val = 42) { return 2; }
};

int main()
{
 MyObj o;

 typedef int(MyObj::*fooptr)(const char*);
 fooptr fp = &MyObj::foo;
 int r1 = (o.*fp)("Hello, foo.");

 typedef int(MyObj::*barptr)(int);
 barptr bp1 = &MyObj::bar;
 int r2 = (o.*bp1)(); // <-- ERROR: too few arguments for call

 typedef int (MyObj::*barptr2)();
 barptr2 bp2 = &MyObj::bar; // <-- ERROR: Can't convert from int(MyObj::*)(int) to int(MyObj::*)(void)
 int r3 = (o.*bp2)();
    return 0;
}

如果我不想为默认参数指定任何值,关于如何使用符合 C++ 的方法有什么想法吗?

编辑:澄清一下限制。我不想在调用或任何 typedef 中指定任何默认参数。例如,我不想这样做:

typedef int(MyObj::*barptr)(int = 5);

...我也不想这样做:

typedef int(MyObj::*barptr)(int);
...
(o.barptr)(5);

【问题讨论】:

  • 只需调用 o.foo("blah") 和 o.bar()?
  • @Goz:请再次阅读帖子。我正在尝试使用函数指针。
  • 原因 默认参数起作用是编译器可以看到它。仅使用函数指针,任何像默认值这样的信息都完全消失了。你必须指定它,或者做一个包装器。
  • @GMan:对,这正是我遇到的问题。我认为可能有一个我没有想到的新颖解决方案,正如我所说,我从来没有发现任何我在标准 C++ 中做不到的事情。
  • @John:不,您只需要指定参数。正如 Andrey 所说,函数指针与它们绑定的函数的关系为零。

标签: c++


【解决方案1】:

期望函数指针按照您期望它们在示例中的工作方式工作是相当奇怪的。 “默认参数”是一个纯粹的编译时概念,它是语法糖的一种形式。尽管在函数声明或定义中指定了默认参数,但它们实际上与函数本身无关。实际上,默认参数在调用点被替换,即它们在 调用者 的上下文中处理。从函数的角度来看,用户提供的显式参数与编译器隐式提供的默认参数没有区别。

另一方面,函数指针是运行时实体。它们在运行时初始化。在运行时默认参数根本不存在。 C++ 中没有“运行时默认参数”这样的概念。

一些编译器允许你在函数指针声明中指定默认参数,如

void foo(int);

int main() {
   void (*pfoo)(int = 42) = foo;
   pfoo(); // same as 'pfoo(42)'
}

但这不是标准的 C++ 并且这似乎不是您要寻找的,因为您希望“默认参数”值在运行时根据指针指向的函数进行更改。

只要你想坚持使用真正的函数指针(而不是函数对象,也就是函子),直接的解决方法就是为你的函数提供一个不同名称的无参数版本,如

class MyObj 
{ 
public: 
  ...
  int bar(int val = 42) { return 2; } 
  int bar_default() { return bar(); }
}; 

int main() 
{ 
  MyObj o; 

  typedef int (MyObj::*barptr2)(); 
  barptr2 bp2 = &MyObj::bar_default;
  int r3 = (o.*bp2)(); 
  return 0; 
} 

当然,这远非优雅。

实际上可以说,我在上面对bar_default 所做的事情可能是由编译器隐式完成的,作为一种语言特性。例如。给定类定义

class MyObj 
{ 
public: 
  ...
  int bar(int val = 42) { return 2; } 
  ...
}; 

人们可能期望编译器允许以下内容

int main() 
{ 
  MyObj o; 

  typedef int (MyObj::*barptr2)(); 
  barptr2 bp2 = &MyObj::bar;
  int r3 = (o.*bp2)(); 
  return 0; 
} 

指针初始化实际上会强制编译器为MyObj::bar 隐式生成一个“适配器”函数(与我之前示例中的bar_default 相同),并将bp2 设置为指向该适配器。但是,目前 C++ 语言中还没有这样的功能。而要引入这样的东西需要比第一眼看起来更多的努力。

另请注意,在最后两个示例中,指针类型为int (MyObj::*)(),与int (MyObj::*)(int) 不同。这实际上是对您的一个问题(因为您在示例中都尝试了):希望它如何工作?使用int (MyObj::*)() 指针?还是使用int (MyObj::*)(int) 指针?

【讨论】:

  • Re:“默认参数”是一个纯粹的编译时概念——是的,但我希望因为编译器知道我通过指针调用的同一个翻译单元中的默认值,所以将是一些让它咳出信息的方法。
  • @John:编译器怎么知道函数指针指向什么?直到运行时才绑定;并且 C++ 编译器不必进行静态分析。
  • @John:我想说的是,在任何情况下,如果函数指针的使用实际上是合理的,这将非常昂贵。实现这一点的唯一合理方法是将所有这些信息填充到指针本身中,即使用“胖”指针。这在 C++ 中并没有这样做。如果您需要“胖”指针,请改用仿函数。
  • @AndreyT:我怀疑我对何时使用函数指针是合理的估计比你的更宽松。 :)
  • @John Dibling:在您的情况下,编译器“很容易”弄清楚参数应该是什么,因为指针绑定对编译器来说是显而易见的。但通常,当绑定如此明显时,根本不需要指针(如您的人工示例中所示)。一旦绑定真正运行时,编译器就不可能正确确定默认参数值(除非它将这些值填充到指针本身或使用我在答案末尾描述的方法)。
【解决方案2】:

当然,您可以创建仿函数而不是函数指针。

struct MyFunctor {
    int operator() {
        return myobj.bar();
    }

    MyFunctor(MyObj &obj) : myobj(obj) {}
    MyObj &myobj;
};

然后:

MyFunctor myfunc(o);
myFunctor();

【讨论】:

  • 使用这个函子和直接调用o.bar()有什么不同?
  • @Manuel:使用不会像创建一个仿函数那么简单,然后立即调用它。相反,您会将仿函数对象提供给不知道 MyObj 是什么或 bar 函数做什么的其他对象。它只会知道它上面有带有 () 运算符的东西。
  • @Manuel:不是。这就是为什么我只说这给了我一个想法。这不是我想要的解决方案。
  • @villintehaspam - 你就不能想办法用 boost::bind 或类似的东西来自动化吗?
  • @Manuel:由于 boost::bind 会使用成员函数指针,我假设 boost::bind 不起作用。
【解决方案3】:

鉴于限制,这是不可能的。您的选择是:

  1. 使用函数包装器。
  2. 使用函子。

查看 Boost 以获取一些方便的工具来简化此操作。

【讨论】:

【解决方案4】:

任务:假设你有以下:

class Thing {
    public:
        void foo (int, double = 3.14) const {std::cout << "Thing::foo(int, double = 3.14) called.\n";}
        void goo (int, double = 1.5) const {std::cout << "Thing::goo(int, double = 1.5) called.\n";}
};

void function1 (const Thing& thing, int a, int b, double c) {
    // Code A
    thing.foo(a,c);
    // Code B
    thing.foo(b);
    // Code C
}

void function2 (const Thing& thing, int a, int b, double c) {
    // Code A
    thing.goo(a,c);
    // Code B
    thing.goo(b);
    // Code C
}

我们想写一个辅助函数来捕获function1和function2,这样重复的代码A、B、C就不需要写两次了。

以下将无法编译:

class Thing {
    public:
        void foo (int, double = 3.14) const {std::cout << "Thing::foo(int, double = 3.14) called.\n";}
        void goo (int, double = 1.5) const {std::cout << "Thing::goo(int, double = 1.5) called.\n";}
};

void functionHelper (const Thing& thing, int a, int b, double c, void (Thing::*f)(int, double) const) {
    // Code A
    (thing.*f)(a,c);
    // Code B
//  (thing.*f)(b);  // Won't compile.  Too few arguments passed to (thing.*f), which expects (int, double).
    // Code C   
}

void function1 (const Thing& thing, int a, int b, double c) {
    functionHelper (thing, a, b, c, &Thing::foo);
}

void function2 (const Thing& thing, int a, int b, double c) {
    functionHelper (thing, a, b, c, &Thing::goo);
}

第一个解决方案(Thing::fooThing::goo 的重载):

#include <iostream>

class Thing {
    public:
        void foo (int, double = 3.14) const {std::cout << "Thing::foo(int, double = 3.14) called.\n";}
        void foo_default (int a) const {
                std::cout << "Thing::foo_default(int) called.\n";
                foo(a);
        }
        void goo (int, double = 1.5) const {std::cout << "Thing::goo(int, double = 1.5) called.\n";}
        void goo_default (int a) const {
                std::cout << "Thing::goo_default(int) called.\n";
                goo(a);
        }
};

void functionHelper (const Thing& thing, int a, int b, double c,
        void (Thing::*f)(int, double) const, void (Thing::*g)(int) const) {
    // Code A
    (thing.*f)(a,c);
    // Code B
    (thing.*g)(b);  // This will compile now, since (thing.*g) expects int only as argument.
    // Code C   
}

void function1 (const Thing& thing, int a, int b, double c) {
    functionHelper (thing, a, b, c, &Thing::foo, &Thing::foo_default);
}

void function2 (const Thing& thing, int a, int b, double c) {
    functionHelper (thing, a, b, c, &Thing::goo, &Thing::goo_default);
}

int main() {
    Thing thing;
    function1 (thing, 2, 5, 1.8);
    std::cout << '\n';
    function2 (thing, 2, 5, 1.8);
}

输出:

Thing::foo(int, double = 3.14) called.
Thing::foo_default(int) called.
Thing::foo(int, double = 3.14) called.

Thing::goo(int, double = 1.5) called.
Thing::goo_default(int) called.
Thing::goo(int, double = 1.5) called.

第二种解决方案(将Thing::fooThing::goo 包装成函数对象):

#include <iostream>
#include <memory>

class Thing {
    public:
        void foo (int, double = 3.14) const {std::cout << "Thing::foo(int, double = 3.14) called.\n";}
        void goo (int, double = 1.5) const {std::cout << "Thing::goo(int, double = 1.5) called.\n";}
        
        class FooOrGoo {
            public: 
                void operator()(const Thing& thing, int a) const {helper1 (thing, a);}
                void operator()(const Thing& thing, int a, double b) {helper2 (thing, a, b);}
                virtual ~FooOrGoo() {std::cout << "Thing::FooOrGoo object destroyed.\n";}
            private:
                virtual void helper1 (const Thing& thing, int a) const = 0;
                virtual void helper2 (const Thing& thing, int a, double b) const = 0;
        };

        class Foo : public FooOrGoo {
            virtual void helper1 (const Thing& thing, int a) const override {thing.foo(a);}
            virtual void helper2 (const Thing& thing, int a, double b) const override {thing.foo(a, b);}
        };

        class Goo : public FooOrGoo {
            virtual void helper1 (const Thing& thing, int a) const override {thing.goo(a);}
            virtual void helper2 (const Thing& thing, int a, double b) const override {thing.goo(a, b);}
        };
};

void functionHelper (const Thing& thing, int a, int b, double c, std::unique_ptr<Thing::FooOrGoo> f) {
    // Code A
    (*f)(thing, a,c);
    // Code B
    (*f)(thing, b);
    // Code C   
}

void function1 (const Thing& thing, int a, int b, double c) {
    functionHelper (thing, a, b, c, std::unique_ptr<Thing::Foo>(new Thing::Foo));  // 'std::make_unique<Thing::Foo>());' is not supported by GCC 4.8.1.
}

void function2 (const Thing& thing, int a, int b, double c) {
    functionHelper (thing, a, b, c, std::unique_ptr<Thing::Goo>(new Thing::Goo));  // 'std::make_unique<Thing::Goo>());' is not supported by GCC 4.8.1.
}

int main() {
    Thing thing;
    function1 (thing, 2, 5, 1.8);
    std::cout << '\n';
    function2 (thing, 2, 5, 1.8);
}

输出:

Thing::foo(int, double = 3.14) called.
Thing::foo(int, double = 3.14) called.
Thing::FooOrGoo object destroyed.

Thing::goo(int, double = 1.5) called.
Thing::goo(int, double = 1.5) called.
Thing::FooOrGoo object destroyed.

您认为哪种解决方案更好?我觉得第二个更优雅,但是代码行数更多(没有多态我做不到)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-06-16
    • 2015-09-15
    • 1970-01-01
    • 2022-09-20
    • 2011-10-20
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多