强制派生类重写至少一个虚函数

发布于 2024-11-13 15:18:56 字数 287 浏览 8 评论 0原文

想象一下这个简单的基类:

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

我想阻止用户从此类派生而不重写其中至少一个类,并阻止它们实例化 simple_http_service

是否有一些nice方法来做到这一点?

Imagine this simple base class:

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

I'd like to prevent the user from deriving from this class without overriding at least one of these and prevent them from instantiang simple_http_service

Is there some nice way to do this?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

空名 2024-11-20 15:18:56

这听起来像是一个非常奇怪的限制。无论如何都要保护用户免受不正确的使用,但不要试图禁止那些你“看不到意义”的事情。如果在不重写这三个函数中的任何一个的情况下从类派生是没有意义的,那么让用户根据自己的喜好重写尽可能多或尽可能少的函数,并相信他不会在不重写的情况下做毫无意义的派生事情 任何函数。用户这样做并没有什么坏处,只是没有多大用处。

但如果您确实需要强制执行这一点(再次,我建议您重新考虑),那么就不要使用虚函数。相反,传递函数指针或函数对象(或 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;
};

现在只需添加必要的构造函数(或自由/静态函数,以便您可以命名它们以避免歧义),以便仅在提供至少一个函数对象时才能实例化该类。

That sounds like a really odd constraint. By all means protect the user from incorrect usage, but don't try to prohibit things that you just "can't see the point of". If there's no point in deriving from your class without overriding any of the three functions, then let the user override as many or as few function as he likes, and trust that he won't do the pointless thing of deriving without overriding any of the functions. There's no harm in the user doing that, it's just not very useful.

But if you do need to enforce this (again, I'd suggest you rethink), then don't use virtual functions. Instead, pass function pointers or function objects (or std::function/boost::function) callbacks. Make the base class look something like this:

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

Now just add the necessary constructors (or free/static functions so you can name them to avoid ambiguity) so that the class can only be instantiated when at least one of the function objects are supplied.

青瓷清茶倾城歌 2024-11-20 15:18:56

我认为所有这些功能都应该是纯虚拟的。您发布的结构实际上是一个接口。如果这些函数不是全部都需要,则派生结构应该只为与它们不相关的函数提供一个空实现。

I think all of these functions should be pure virtual. The struct you have posted is effectively an interface. If the functions are not all required, the derived structs should merely provide an empty implementation for the functions that are not relevant to them.

新一帅帅 2024-11-20 15:18:56

如果您只是想强制基类是抽象的,请给它一个纯虚拟析构函数,并使其他函数成为普通的虚拟函数。

If you simply want to enforce the base class being abstract, give it a pure virtual destructor, and make your other functions ordinary virtual ones.

压抑⊿情绪 2024-11-20 15:18:56

如果您知道希望派生类重写哪些方法,只需声明该方法 纯虚拟

例如,要使 http_get 成为纯虚拟:

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

If you know which methods that you want the deriving class to override, simply declare that method pure virtual.

For example, to make http_get a pure virtual:

struct simple_http_service
{
  virtual reply http_get(…) = 0;
  virtual reply http_post(…);
  virtual reply http_delete(…);
  // etc.
};
‘画卷フ 2024-11-20 15:18:56

我不太明白为什么要为其他两个函数提供默认实现,但在 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

检查派生类是否实现了某些功能的问题基类虚函数位于此处

I don't not quite understand why do you want to provide default implementation for other two functions but require at least one of them to be user-defined in the case of http requests.
It's clear if all functions are using each other to implement some functionality using existing code.
Imagine the example with that class:

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

There is a class that is string-convertible and string-serializable. But if one of these is not implemented, you want to call the second instead, so that would be an option:

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(){}

But now there is the problem with deriving from Cls but not overriding at least one of the functions. If no overriding is made, at runtime you just enter infinite recursion. If this is one of your problems, there is a run-time solution, the code below is just not doing anything if such a problem occurs.

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(){}

This compiles on GCC, but fills your screen with warnings about strange convertion from funcptr to void*. At least it works as intended. There may be some metaprogramming compile-time solutions, need to think about it.

Appendix1, testing comparison between member funcs:

It is really weird

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

Appendix2, steps of getting real address of the function:

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

The issue of checking if the derived class has implemented some of the base class virtual functions is here

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文