2011-01-05 68 views
2

我的代码旨在管理联网客户端和服务器上的操作,因为两者之间存在重大的重叠。但是,这里和那里有一些功能是专门由客户端或服务器调用的,并且在服务器上意外地调用客户端功能(反之亦然)是错误的重要来源。如何将代码自动添加到C++中的衍生函数中

为了减少这些类型的编程错误,我试图给函数添加标签,以便在错误使用时会引发骚动。我目前的解决方案是在每个函数的开始处有一个简单的宏,如果客户端或服务器访问它们不应该访问的成员,则调用assert。然而,当有多个派生实例的类时,这会遇到问题,因为我必须在每个子类中将实现标记为客户端或服务器端。

我想要做的就是在基类中的虚拟成员签名中放置一个标签,这样我只需标记一次即可,而不会因为忘记重复执行而出现错误。我曾考虑在基类实现中进行检查,然后用base :: functionName之类的方式来引用它,但只要需要手动将函数调用添加到每个实现中,就会遇到同样的问题。理想情况下,我可以像默认构造函数一样自动调用父级版本。

有人知道如何在C++中实现这样的东西吗?我应该考虑另一种方法吗?

谢谢!

+4

听起来像你应该有不同的代码和/或类为服务器/客户端 – Falmarri 2011-01-05 01:04:09

+2

为什么不把通用代码分解成基类,并从它们派生一个服务器类和客户端类? – Puppy 2011-01-05 01:12:09

回答

5

另一种方法可能会覆盖一个比一个不同的方法,您的呼叫者实际调用:

class Base { 
public: 
    void doit(const Something &); 
protected: 
    virtual void real_doit(const Something &); 
}; 

class Derived: public Base { 
protected: 
    virtual void real_doit(const Something &); 
}; 

Base::doit()实施能做的检查,以确保它被称为在合适的环境,然后调用虚拟real_doit()函数。派生类将覆盖受保护的虚函数,任何一个类的用户都将无法调用受保护的函数。

Base::doit()函数是不是虚拟的,所以派生类不会无意中覆盖错误的。 (人们可以尝试,但希望他们很快会注意到,当它不叫。)

+1

+1:这被称为*非虚拟接口习语*,并且只允许执行一次前置条件和后置条件。 – 2011-01-05 07:51:06

+0

谢谢。这似乎是我的目的最干净,破坏性最小的方法。此外,感谢Matthieu发布成语的名称。虽然我以前见过并使用过它,但我并没有意识到这是一种标准方法。 – Ian 2011-01-05 19:13:04

3

你提出的是非常复杂的。这听起来像一个简单的解决方案将是

class CommonStuff { 
    // all common code that anybody can safely call 
}; 

class ServerBase : public CommonStuff { 
    // only what the server is allowed to call; can safely be overwritten 
}; 

class ClientBase : public CommonStuff { 
    // only what the client is allowed to call; can safely be overwritten 
}; 

编译时强制执行比任何种类的运行时执行的要好得多。

+0

+1:即使我提出了Greg的答案,因为NVI是一个很好的习惯用法,但我并不真正理解一个人如何能够进入这样一种情况,即一个班级拥有永远不应该被调用的方法......这是一种代码味道就我而言。 – 2011-01-05 07:52:27

1

在没有重新设计类的情况下,你所要求的语言(我知道)没有办法。最简单的解决方案可能是具有不声明服务器功能的接口(纯虚拟)类和不声明客户端函数的接口类,并且让您的合并代码从两个接口继承(公开)。然后在您的客户端程序中,使用一个参考(或指针)到Client接口,该接口不允许访问未在Client接口中声明的任何方法。在服务器上,使用Server接口。

这也允许您使用派生类,如ServerClient

1

我会考虑把这个库分成三个库:一个基本的库,它包含了所有的东西,一个服务器库和一个客户端库。只要客户端不使用服务器库,就很好。你可能会添加一些额外的类(类Processor可能分裂成BaseProcessorClientProcessor,并且ServerProcessor,其中每个子类都有基本没有一个附加功能。)

如果这是行不通的,你能把服务器/客户端检查放在类的构造函数中,并在那里调用断言? (这只适用于服务器专用或客户专用的服务器,而不适用于该方法。)

如果这样做不起作用,那么实际编译不同版本的库会有什么意义,基于它是服务器还是客户端版本?将方法及其声明与#ifdef SERVERBUILD#ifdef CLIENTBUILD环绕,并包含一些检查以确保它们都未定义(#if defined(SERVERBUILD) && defined(CLIENTBUILD)#error Can't define both!)。

0

我投了Greg Hewgill的回答,但它让我想到了添加“方面”的方法,例如您的要求。我在这里用自己的命名约定(类Base和方法doit):

class Base { 
protected: 
    class Aspect { 
    public: 
     Aspect(int x) { 
      std::cout << "aspect" << std::endl; 
     } 
    }; 
public: 
    virtual void doit(const Something &arg, const Aspect hook = 0) 
    { 
     std::cout << "doit(" << arg << ")" << std::endl; 
    } 
}; 

呼叫者只能说base.doit(arg)因为Aspect是默认参数。它的构造函数在doit之前运行,并且它的析构函数(未绘制)在之后运行。可悲的是,我的第一个想法是使默认参数hook = this不被允许。

孩子可以覆盖doit具有相同的签名,并获得相同的效果。

+0

我想我明白你的意思了。因此,如果客户端试图调用它,那么您将拥有一个“ServerOnly”方面类,该类将在其讲师中声明一个断言? – Ian 2011-01-05 01:54:04

+0

我真的很喜欢这种方法,如果我定义一个像“#define SERVER_ONLY_RESTRICTION const RestrictServerOnly _hook = 0”这样的宏,它会应用默认参数,给我看起来像“virtual void doit(const Something &arg,SERVER_ONLY_RESTRICTION);“但我应该注意的一点是,该方面需要被保护而不是私人的,否则子类无法从基本功能派生出来! – Ian 2011-01-05 02:32:20

+0

我刚刚意识到Aspect类只能访问具有全局范围的信息,当我需要它可以访问基类的成员时。我想不出一种给予嵌套类访问的方法,因为 - 正如你已经说过的 - “hook = this”不允许作为默认参数。 – Ian 2011-01-05 02:53:51

相关问题