Заставить производный класс переопределить хотя бы одну виртуальную функцию - PullRequest
4 голосов
/ 04 июня 2011

Представьте себе этот простой базовый класс:

struct simple_http_service
{
  virtual reply http_get(…);
  virtual reply http_post(…);
  virtual reply http_delete(…);
  // etc.
};

Я бы хотел запретить пользователю выходить из этого класса, не переопределяя хотя бы один из них, и предотвратить его инстанцию ​​simple_http_service

Есть ли какой-нибудь хороший способ сделать это?

Ответы [ 5 ]

5 голосов
/ 04 июня 2011

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

Но если вам нужно необходимо применить это (опять же, я бы посоветовал вам переосмыслить), то не используйте виртуальные функции. Вместо этого передайте указатели функций или объекты функций (или std::function / boost::function) обратные вызовы. Сделайте так, чтобы базовый класс выглядел примерно так:

struct simple_http_service
{
  typedef std::function<reply (...)> func_type;
  reply http_get(...) { return get_func(...); }
  reply http_post(...) { return post_func(...); }
  reply http_delete(...) { return delete_func(...); }
  // etc.

private:
  func_type get_func;
  func_type post_func;
  func_type delete_func;
};

Теперь просто добавьте необходимые конструкторы (или свободные / статические функции, чтобы их можно было назвать по именам, чтобы избежать неоднозначности), чтобы экземпляр класса мог быть создан только тогда, когда указан хотя бы один из объектов функции.

1 голос
/ 04 июня 2011

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

1 голос
/ 04 июня 2011

Я думаю, что все эти функции должны быть чисто виртуальными. Структура, которую вы разместили, по сути является интерфейсом. Если не все функции необходимы, производные структуры должны просто предоставлять пустую реализацию для функций, которые не имеют к ним отношения.

0 голосов
/ 26 мая 2019

Я не совсем понимаю, почему вы хотите предоставить реализацию по умолчанию для двух других функций, но требуется, чтобы хотя бы одна из них была определена пользователем в случае http-запросов. Понятно, что все функции используют друг друга для реализации некоторых функций с использованием существующего кода. Представьте себе пример с этим классом:

class Cls
{
  public:
    virtual std::string toString()=0;
    virtual std::string serialize()=0;
};

Существует класс, который можно преобразовать в строку и сериализовать в строку. Но если один из них не реализован, вам нужно вместо этого вызвать второй, так что это будет вариант:

class Cls
{
  public:
    virtual std::string toString() //calls serialize() by default
    {
      return this->serialize();
    }
    virtual std::string serialize() //calls toString()
    {
      return this->toString();
    }
    virtual ~Cls()=0; //force the class to be abstract
};  Cls::~Cls(){}

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

class Cls
{
  public:
    virtual std::string toString()
    {
      if ((void*)(this->*(&Cls::serialize)) != (void*)(&Cls::serialize))
      {//checks if the current implemetation is not equal to the default one
        return this->serialize();
      }
      else
      {
        return ""; //default return value
      }
    }

    virtual std::string serialize()
    {
      if ((void*)(this->*(&Cls::toString))!=(void*)((&Cls::toString)))
      {
        return this->toString();
      }
      else
      {
        return "";
      }
    }
    virtual ~Cls()=0;
};  Cls::~Cls(){}

Это компилируется в GCC, но заполняет ваш экран предупреждениями о странном преобразовании из funcptr в void *. По крайней мере, это работает как задумано. Могут быть некоторые метапрограммирующие решения времени компиляции, нужно подумать об этом.

Приложение 1 , тестирование сравнения между функциями-членами:

Это действительно странно

#include <iostream>

class Base
{
    public:
        virtual int test()
        {
            //default imp
            return 0;
        }
};

class Der : public Base
{
    public:
        int test() override
        {
            //custom imp
            return 1;
        }
};

int main()
{
    Der a;
    Base b;
    std::cout << ((&Der::test) == (&Base::test)) << std::endl;//1: wrong
    //they are not equal
    //but for some reason the output is "true"
    //so direct comparisons do not work
    //however
    //if you convert that pointer to void*
    //everything works
    std::cout << ((void*)(&Der::test) == (void*)(&Base::test) ) << std::endl;      //0:right
    std::cout << ((void*)(a.*(&Base::test)) == (void*)(&Base::test) ) << std::endl;//0:right
    std::cout << ((void*)(b.*(&Base::test)) == (void*)(&Base::test) ) << std::endl;//1:right
    std::cout << ((void*)(&(a.test)) == (void*)(&(b.test)) ) << std::endl;         //0:right
    //so if you want to compare two functions
    //cast them to void*
    //works in any cases
    //'-Wno-pmf-conversions' compiler flag to inhibit warnings about casting
    system("pause");
    return 0;
}

Приложение2 , этапы получения реального адреса функции:

Cls::serialize; //the function object itself
&Cls::serialize; //its member pointer
(void*)(&Cls::serialize); //extracting real address of the function for the comparison
(this->*&Cls::serialize); //again, a member pointer
(void*)(this->*&Cls::serialize); //extracting real address
//  │        │  └── Getting "pointer" to a member function of the class
//  │        └───── Then binding 'this' to that function, recall that if the function is virtual, '->*' returns a mamber pointer to it's custom implementation, not the default one.
//  └────────────── Then getting the real address


// it looks like 'this->*&Cls::serialize' does the same as '&this->serialize'
// but in practice it's not quite right
// '&this->serialize' returns the function pointer based on 'this' type
// therefore, comparison breaks, as inside of a base class 'this' always has the base type
// so you always receive the default implementation pointer
// 'this->*&Cls::serialize' does the same
// but now if 'serialize' is virtual
// it takes it into account and sends back its' custom implementation pointer

// (void*) casting is required because you need to compare functions' real addresses
// if you compare member pointers of a single virtual function
// they seem to be equal while they are, in fact, not

Проблема проверки, реализовал ли производный класс некоторые виртуальные функции базового класса, здесь

0 голосов
/ 04 июня 2011

Если вы знаете, какие методы вы хотите переопределить производным классом, просто объявите этот метод pure virtual .

Например, чтобы сделать http_get чисто виртуальным:

struct simple_http_service
{
  virtual reply http_get(…) = 0;
  virtual reply http_post(…);
  virtual reply http_delete(…);
  // etc.
};
...