Есть ли способ объявить указатель только на конкретный метод? - PullRequest
0 голосов
/ 05 сентября 2018

У меня есть класс:

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, который принимает только определенную именованную функцию, не может быть объявлен.

Ответы [ 4 ]

0 голосов
/ 05 сентября 2018

Правильный совет Марека рассмотреть возможность использования большего количества C ++ - несмотря на идиоматические альтернативы, ваше желание разумно и может быть удовлетворено простым const.

Причина, по которой кто-то может захотеть это сделать, та же, что и для всех других определений const (которые все могут быть в основном заменены правой частью их определения): кто-то хочет иметь символическое имя, чтобы значение конкретного Значение понятно в контексте его использования, и каждый хочет иметь одну точку обслуживания в случае, если он хочет изменить значение const. Вот почему все недовольны целочисленными литералами в коде.

Эти точные причины применимы к определению const int с и void (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;
}
0 голосов
/ 05 сентября 2018

В комментариях вы сказали, что задаете чисто академический вопрос, поэтому позвольте мне дать чисто академический ответ. Вы не можете иметь указатель только на один метод, потому что все эти методы имеют одинаковый тип. Что вы можете сделать, так это сделать их разными:

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, который предложил сделать то же самое.

0 голосов
/ 05 сентября 2018

Вы не можете определить тип, который равен только «одно конкретное значение этого другого типа», например, тип, единственное существующее значение которого точно равно &Car::closeDoor.

Одна вещь, которую вы можете сделать, это достать швейцарский армейский нож программирования - ввести уровень косвенности - и определить тип, который представляет одно конкретное значение, оборачивая его во что-то еще.

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

Синтаксис немного неудачен, потому что, очевидно, оператор .* не выполняет неявного преобразования своего правого операнда.

Конечно, это просто очень окольный и запутанный способ простого вызова функции, указанной типом.

Было бы более полезно с типом, который может содержать предопределенное подмножество функций (например, типа DoorOperation или Opener).
Вы, вероятно, можете создать это, используя умное программирование шаблонов, но я не умный программист шаблонов.

0 голосов
/ 05 сентября 2018

Этот тип указателей void (Car::*action)(int) используется редко. В настоящее время есть три предпочтительных способа приблизиться к этому:

  • параметр шаблона (например, алгоритмы STL)
  • интерфейс (чистый абстрактный класс)
  • std::function<void()> и или лямбды

Очевидно, вам нужно второе решение:

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. Этот указатель может указывать на любую опечатку объекта, который реализует этот интерфейс.

...