C ++上的部分类定义?
有谁知道在C ++上是否可以有部分类定义?
就像是:
file1.h:
class Test { public: int test1(); };
file2.h:
class Test { public: int test2(); };
对我而言,定义具有跨平台独立功能的多平台类似乎很有用,因为继承是一种代价,对多平台类没有用处。
我的意思是,仅在编译时,我们永远不会在运行时拥有两个多平台专业化实例。继承对于满足公共接口需求可能是有用的,但是在此之后,它不会在运行时添加任何有用的东西,而只是增加成本。
另外,我们将不得不使用丑陋的#ifdef来使用该类,因为我们不能从抽象类中创建实例:
class genericTest { public: int genericMethod(); };
然后让我们说一下win32:
class win32Test: public genericTest { public: int win32Method(); };
有可能:
class macTest: public genericTest { public: int macMethod(); };
假设win32Method()和macMethod()都调用genericMethod(),那么我们将必须使用如下所示的类:
#ifdef _WIN32 genericTest *test = new win32Test(); #elif MAC genericTest *test = new macTest(); #endif test->genericMethod();
现在想一想,继承仅对于给他们两个都依赖于特定于平台的genericMethod()有用,但是因此我们要花费两个构造函数。同样,我们在代码周围散布了难看的#ifdef。
这就是为什么我要寻找局部类的原因。我可以在编译时定义特定于平台的部分结束端,当然,在这个愚蠢的示例上,我仍然需要在genericMethod()中添加一个丑陋的#ifdef,但是还有另一种方法可以避免这种情况。
解决方案
尝试继承
具体来说
class AllPlatforms { public: int common(); };
接着
class PlatformA : public AllPlatforms { public: int specific(); };
这在C ++中是不可能的,它将给我们有关重新定义已定义类的错误。如果我们想分享行为,请考虑继承。
按照书面规定,这是不可能的。
我们可能要研究名称空间。我们可以将函数添加到另一个文件中的名称空间。类的问题是每个.cpp需要查看类的完整布局。
没有。
但是,我们可能需要查找一种称为"策略类"的技术。基本上,我们可以创建微类(它们本身没有用),然后在以后将它们粘合在一起。
两次声明一个类体可能会产生类型重定义错误。如果我们正在寻找解决方法。我建议#ifdef'ing,或者使用Abstract Base Class隐藏平台特定的细节。
如Jamie所说,可以使用继承,也可以使用#ifdef来使不同的部分在不同的平台上进行编译。
由于标头只是按文本插入的,所以其中一个可以省略" Test {"和"}"类,并在另一个中间包含#include。
我实际上已经在生产代码中看到了这一点,尽管Delphi不是C ++。这特别让我烦恼,因为它破坏了IDE的代码导航功能。
For me it seems quite useful for definining multi-platform classes that have common functions between them that are platform-independent.
除了开发人员几十年来没有这种"功能"之外,他们一直在这样做。
我相信,partial的创建是因为Microsoft几十年来一直有一种不良习惯,即生成代码并将其交给开发人员进行开发和维护。
生成的代码通常是维护的噩梦。当需要更改MFC版本时,整个MFC生成的框架有什么习惯?或者在升级Visual Studio时如何将所有这些代码移植到* .designer.cs文件中?
大多数其他平台更严重地依赖于生成配置文件,而不是用户/开发人员可以修改的文件。词汇量有限且不易与无关代码混合的代码。如果认为必要,甚至可以将配置文件作为资源文件插入二进制文件中。
我从未见过在继承或者配置资源文件做不到更好的地方使用"部分"的情况。
我们可以使用模板专门化和部分专门化获得类似部分类的信息。在投入太多时间之前,请检查编译器对此的支持。像MSC ++ 6.0这样的较旧的编译器不支持部分专业化。
或者我们可以尝试PIMPL
通用头文件:
class Test { public: ... void common(); ... private: class TestImpl; TestImpl* m_customImpl; };
然后创建cpp文件,以执行特定于平台的自定义实现。
这个怎么样:
class WindowsFuncs { public: int f(); int winf(); }; class MacFuncs { public: int f(); int macf(); } class Funcs #ifdef Windows : public WindowsFuncs #else : public MacFuncs #endif { public: Funcs(); int g(); };
现在,Funcs
是在编译时已知的类,因此抽象基类或者其他任何类都不会造成开销。
#include will work as that is preprocessor stuff. class Foo { #include "FooFile_Private.h" } //////// FooFile_Private.h: private: void DoSg();
我们不能在C ++中部分定义类。
这是一种获得"多态性,只有一个子类"的效果的方法,而且没有开销,并且几乎没有#define或者代码重复。称为模拟动态绑定:
template <typename T> class genericTest { public: void genericMethod() { // do some generic things std::cout << "Could be any platform, I dunno" << std::endl; // base class can call a method in the child with static_cast (static_cast<T*>(this))->doClassDependentThing(); } }; #ifdef _WIN32 typedef Win32Test Test; #elif MAC typedef MacTest Test; #endif
然后在其他一些标题中,我们将获得:
class Win32Test : public genericTest<Win32Test> { public: void win32Method() { // windows-specific stuff: std::cout << "I'm in windows" << std::endl; // we can call a method in the base class genericMethod(); // more windows-specific stuff... } void doClassDependentThing() { std::cout << "Yep, definitely in windows" << std::endl; } };
和
class MacTest : public genericTest<MacTest> { public: void macMethod() { // mac-specific stuff: std::cout << "I'm in MacOS" << std::endl; // we can call a method in the base class genericMethod(); // more mac-specific stuff... } void doClassDependentThing() { std::cout << "Yep, definitely in MacOS" << std::endl; } };
这样可以在编译时为我们提供适当的多态性。 genericTest可以通过非虚拟方式调用doClassDependentThing来为其提供平台版本(几乎类似于虚拟方法),并且当win32Method调用genericMethod时,它当然会获取基类版本。
这样就不会产生与虚拟调用相关的开销,就像我们键入两个没有共享代码的大类一样,我们可以获得相同的性能。它可能在con(de)构造时产生非虚拟的调用开销,但是如果内联genericTest的con(de)构造函数,则应该没问题,而且在任何情况下该开销都不会比具有由调用的genericInit方法更糟两个平台。
客户端代码仅创建Test的实例,并且可以在它们的实例上调用genericTest或者平台的正确版本中的方法。为了在不需要平台且不想意外使用平台特定调用的代码中提供类型安全性,我们还可以执行以下操作:
#ifdef _WIN32 typedef genericTest<Win32Test> BaseTest; #elif MAC typedef genericTest<MacTest> BaseTest; #endif
使用BaseTest时要格外小心,但要比在C ++中使用基类时要多得多。例如,请勿使用错误判断的传递值对其进行切片。并且不要直接实例化它,因为如果我们这样做并调用了一个最终导致尝试"伪虚拟"调用的方法,则可能会遇到麻烦。可以通过确保所有genericTest的构造函数受到保护来实施后者。
如果我们想扩展类而不接触原始文件,则部分类是很好的选择。例如,我想使用助手扩展std的矢量模板类。为什么我必须创建像vectorEx这样的继承类,而不仅仅是通过partial添加方法?