有没有办法只声明一个指向特定方法的指针?

Is there a way to declare a pointer to a particular method only?

我有一个 class:

class Car
{
    public:
        Car();
        ~Car();
        // nr indicates which particular door.
        void startEngie();
        void openDoor(int nr);
        void closeDoor(int nr); 
        void openWindow(int nr);
        void closeWindow(int nr);
        void turnOnAirConditioner(int vloume);
}

现在 void (Car::*action)(int) 我声明了一个名为 action 的指向 Car 方法的指针。问题是很多方法都可以赋值给它:&Car::openDoor&Car::closeDoor&Car::openWindow&Car::closeWindow&Car::turnOnAirConditioner,但不是&Car::startEngie

我想知道我是否正确:没有办法只声明一个指向特定方法的指针。换句话说:pointer type,它只接受一个特定的命名函数,不能被声明。

这种指针void (Car::*action)(int)很少用到。 现在有三种首选方法来解决这个问题:

  • 模板参数(以STL算法为例)
  • 接口(纯抽象class)
  • std::function<void()> 和/或 lambdas

显然您需要第二种解决方案:

class IWindowOpenable {
public:
    virtual ~IWindowOpenable() {}

    virtual void openWindow(int nr) = 0;
};

class Car : public IWindowOpenable
{
public:
    Car();
    ~Car();

    void startEngie();
    void openDoor(int nr);
    void closeDoor(int nr); 
    void openWindow(int nr);
    void closeWindow(int nr);
    void turnOnAirConditioner(int vloume);
};

Car car;
IWindowOpenable *windowOpener = &Car;

请注意,任何实现接口 IWindowOpenable 的东西都只能在名称为 openWindow 的方法上运行。 该指针可以指向实现该接口的任何拼写错误的对象。

评论里说你问的是纯学术问题,那我就纯学术回答吧。您不能只有一个指向一个方法的指针,因为所有这些方法都具有相同的类型。你可以做的是让它们有不同的类型:

struct Car
{
    struct open_door_tag { };
    struct close_door_tag { };

    void open_left_door(int nr, open_door_tag = {});
    void open_right_door(int nr, open_door_tag = {});
    void close_door(int nr, close_door_tag = {});
};

现在如果你有指针

void (Car::*open_that_door_ptr)(int, Car::open_door_tag);

您对分配有一些限制:

open_that_door_ptr = &Car::open_left_door;   // OK
open_that_door_ptr = &Car::open_right_door;  // OK
//open_that_door_ptr = &Car::close_door;     // Won't compile

不幸的是(或不是),如果不提供标记,您将无法通过此指针调用方法:

Car car;
(car.*open_that_door_ptr)(2, Car::open_door_tag{});
// or (thanks, @Jarod42)
(car.*open_that_door_ptr)(2, {});
// (car.*open_that_door_ptr)(2);     // Won't compile

注意。在我发布这个答案后,我看到@Jarod42 的评论,他建议做基本相同的事情。

您不能定义 只是 "one specific value of this other type" 的类型,例如,其唯一现有值恰好是 &Car::closeDoor.[=15 的类型=]

可以做的一件事是拿起编程的瑞士军刀——引入一个间接级别——并定义一个代表[=29的类型=] 一个特定的值,将其包裹在其他东西中。

// Simplified Car
class Car
{
public:
    void openDoor(int i) { std::cout << "open door\n"; }
    void closeWindow(int i) { std:cout << "close window\n"; }
};

using Fun = void (Car::*)(int);

// A template for an immutable structure, so it 
// can hold only one specific value; the template argument.
template<Fun fun>
struct CarOperation
{
    const Fun f = fun;
};

// Now we can make one type for each operation
using OpenDoor = CarOperation<&Car::openDoor>;
using CloseWindow = CarOperation<&Car::closeWindow>;

int main()
{
    Car c;
    OpenDoor d;
    (c.*d.f)(1);
    CloseWindow e;
    (c.*e.f)(2);
}

语法有点不幸,因为显然 .* 运算符不对其右操作数进行任何隐式转换。

当然,这只是简单调用类型指示的函数的一种非常迂回和混淆的方式。

使用可以包含函数的预定子集的类型(例如,DoorOperationOpener 类型)会更有用。
您可能可以使用聪明的模板编程来构建它,但我不是一个聪明的模板程序员。

尽管 Marek 提出了考虑使用更多 C++ 惯用替代方案的正确建议,但您的愿望是合理的,可以通过简单的 const.

实现

人们可能想要这样做的原因与所有其他 const 定义(原则上都可以用其定义的右侧替换)相同:人们想要一个符号名称,以便具体的值在其使用的上下文中是可以理解的,并且人们希望有一个单点维护以防万一想要更改 const 值。这就是为什么每个人都对代码中的整数文字皱眉。

这些确切的原因同样适用于 const intvoid (Car::*const action)(int) 的定义。

因此,举一个人为的例子,如果您想要一个每次汽车启动时都会调用的函数,作为该特定汽车的不可变 属性,您可以声明一个 atStart常量成员函数指针作为成员(我也引入了一个typedef)。指针必须在汽车的构造函数中初始化,所有 const 成员都必须如此。

#include<iostream>
using namespace std;

struct Car
{
        typedef void (Car::* const CarfuncT)(void);
        Car(CarfuncT atStartArg)
            : atStart(atStartArg) 
        {}
        void startEngine() { cout << "Start Engine\n"; (this->*atStart)(); };
        void turnOnAC() { cout << "AC on\n"; }
        void turnOnHeat() { cout << "Heat on\n"; }
        void turnOnRadio() { cout << "Radio on\n"; }
        void (Car::* const atStart)();
};

用户代码可能如下所示:

int main()
{
  cout << "I'll create a configured car. In which state do you live?\n";

  string state;
  cin >> state;
  Car *car = new Car(state == "Texas" ? &Car::turnOnAC : &Car::turnOnRadio);

  car->startEngine();

  delete car;
}