2008-09-26 41 views
28

任何人都知道是否有可能在C++上有部分类定义?C++上的部分类定义?

喜欢的东西:

file1.h:

 
class Test { 
    public: 
     int test1(); 
}; 

file2.h:

 
class Test { 
    public: 
     int test2(); 
}; 

对于我来说,似乎是definining具有之间的共同功能的多平台类非常有用他们是独立于平台的,因为继承是一种对多平台类无用的支付成本。

我的意思是说,在编译时你永远不会有两个多平台的专门实例。继承对于满足公共接口需求可能很有用,但在此之后它不会在运行时添加任何有用的东西,只是成本。

而且你将不得不使用一个丑陋的#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。

这就是我寻找部分类的原因。我可以在编译时定义具体的平台依赖的部分结束,当然,在这个愚蠢的例子中,我仍然需要一个丑陋的#ifdef在genericMethod()中,但是还有其他方法可以避免这种情况。

+0

首先,非虚函数都是不收取“费用”和第二,如果你的代码不表明哪个版本你正在使用的界面,在一个版本上工作会打破其他版本,例如当你做test-> win32Method()。平台特定的类不一样,并且应该有不同的名字。 – Jamie 2008-09-28 22:03:39

+0

您可以使用工厂方法来最小化#ifdefs。你可以使用模板(http://en.wikipedia.org/wiki/Curiously_Recurring_Template_Pattern)来进一步减少它。 – Jamie 2008-09-28 22:06:50

+0

C++是如此老式和可怜的语言(( – 2015-09-15 07:18:16

回答

32

这是不可能在C++中,它会给你一个错误重新界定已定义的类。如果您想分享行为,请考虑继承。

17

尝试继承

具体

class AllPlatforms { 
public: 
    int common(); 
}; 

然后

class PlatformA : public AllPlatforms { 
public: 
    int specific(); 
}; 
0

正如所写,这是不可能的。

你可能想看看命名空间。您可以将函数添加到另一个文件中的命名空间。一个类的问题是每个.cpp都需要看到该类的完整布局。

1

没有。

但是,您可能需要查找一种名为“策略类”的技术。基本上,你可以制作微型类(它们自己没用),然后在稍后的时间将它们粘合在一起。

+0

很酷!维基百科有一个很好的例子:https://en.wikipedia.org/wiki/Policy-based_design – Andrew 2016-09-26 08:36:55

0

声明一个类体两次可能会产生一个类型重定义错误。如果你正在寻找工作。我建议#ifdef'ing,或使用Abstract Base Class来隐藏平台特定的细节。

2

正如Jamie所说,要么使用继承,要么使#ifdef在不同的平台上编译不同的部件。

1

由于标题只是文本插入,其中一个可以省略“class Test {”和“}”,并在另一个中间包含#include。

我已经在生产代码中看到过这个,虽然Delphi不是C++。它特别让我恼火,因为它打破了IDE的代码导航功能。

+0

这是非常误导,非常误导,但我认为一个有效的解决方案:-) – kervin 2008-09-26 18:05:45

3

对我来说,对于定义具有平台无关性的通用函数的多平台类似乎非常有用。

除开发人员已经这么做了几十年没有这个'功能'。

我相信部分原因是因为微软几十年来一直有一个坏习惯,生成代码并交给开发人员开发和维护。

生成的代码通常是维护的噩梦。整个MFC生成框架时需要修改MFC版本的习惯?或者,如何在升级Visual Studio时将所有代码移植到* .designer.cs文件中?

大多数其他平台更依赖于生成配置文件而不是用户/开发人员可以修改。那些词汇量更有限,不容易混入无关的代码。如果认为有必要,配置文件甚至可以作为资源文件插入到二进制文件中。

我从来没有见过'部分'用于继承或配置资源文件不会做得更好的地方。

+0

如果您有一个模板接口类,您从中继承了CRTP-fashion,此模板接口类也与另一个具有相同特化的模板类交谈,并且从接口派生的类和使用pImpl惯用语具有相同的所有实现公共函数,但必须针对所有私有函数具有不同的特定于平台的实现?部分类将比在基于#ifdef开关的公共接口实现之上包含单独的Impl文件更清晰 – orfdorf 2015-01-12 00:57:41

0

你可以使用模板专业化部分专业得到类似部分类的东西。在你投入太多时间之前,请检查你的编译器对这些的支持。较早的编译器如MSC++ 6.0不支持部分专业化。

8

或者你可以尝试PIMPL

常见的头文件:

class Test 
{ 
public: 
    ... 
    void common(); 
    ... 
private: 
    class TestImpl; 
    TestImpl* m_customImpl; 
}; 

然后创建cpp文件做的基于特定平台的自定义实现。

+0

您需要一个指向TestImpl的指针,而不是实例! – coppro 2008-09-26 18:26:52

+0

哎哟感谢您的支持 – PiNoYBoY82 2008-09-26 18:31:41

3

如何:

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是在编译时已知的类,所以没有开销由抽象基类或什么引起的。

8
#include will work as that is preprocessor stuff. 

class Foo 
{ 
#include "FooFile_Private.h" 
} 

//////// 

FooFile_Private.h: 

private: 
    void DoSg(); 
12

你不能在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的构造函数都受到保护来实施。

0

如果您想在不接触原始文件的情况下扩展类,则部分类很好。例如,我想用助手扩展std的矢量模板类。为什么我必须创建类似vectorEx的继承类,而不是仅仅通过部分添加方法?

1

这在C++中是不可能的,它会给你一个关于重新定义已定义的 类的错误。如果您想分享行为,请考虑继承。

我同意这一点。部分类是奇怪的结构,使得事后维护非常困难。很难找到每个成员声明的哪个部分类,重新定义甚至重新实现要素都难以避免。

你想扩展std :: vector,你必须从它继承。这是由于几个原因。首先你要改变班级的责任,并且(恰当地)改变班级的不变量。其次,从安全的角度来看,这应该避免。 考虑到处理用户身份验证类...

partial class UserAuthentication { 
    private string user; 
    private string password; 
    public bool signon(string usr, string pwd); 
} 

partial class UserAuthentication { 
    private string getPassword() { return password; } 
} 

很多其他原因可以提到...

0

设独立的平台和平台相关的类/函数是每个-其他友元类/功能。:)

而且它们的独立名称标识符允许更好地控制实例化,所以耦合更松散。部分地打破面向对象的封装基础太过于绝对,而必要的朋友声明几乎没有放松它,仅仅足以促进多范式分离焦点,如特定于平台的方面的平台独立方面。

2

肮脏的,但实际的方式是使用的#include预处理:

Test.h:

#ifndef TEST_H 
#define TEST_H 

class Test 
{ 
public: 
    Test(void); 
    virtual ~Test(void); 

#include "Test_Partial_Win32.h" 
#include "Test_Partial_OSX.h" 

}; 

#endif // !TEST_H 

Test_Partial_OSX.h:

// This file should be included in Test.h only. 

#ifdef MAC 
    public: 
     int macMethod(); 
#endif // MAC 

Test_Partial_WIN32.h:

// This file should be included in Test.h only. 

#ifdef _WIN32 
    public: 
     int win32Method(); 
#endif // _WIN32 

Test.cpp的:

// Implement common member function of class Test in this file. 

#include "stdafx.h" 
#include "Test.h" 

Test::Test(void) 
{ 
} 

Test::~Test(void) 
{ 
} 

Test_Partial_OSX.cpp:

// Implement OSX platform specific function of class Test in this file. 

#include "stdafx.h" 
#include "Test.h" 

#ifdef MAC 
int Test::macMethod() 
{ 
    return 0; 
} 
#endif // MAC 

Test_Partial_WIN32.cpp:

// Implement WIN32 platform specific function of class Test in this file. 

#include "stdafx.h" 
#include "Test.h" 

#ifdef _WIN32 
int Test::win32Method() 
{ 
    return 0; 
} 
#endif // _WIN32 
0

我一直在做我的渲染引擎类似的东西。我有一个模板的IResource接口类从各种资源都继承(精简为简洁起见):

template <typename TResource, typename TParams, typename TKey> 
class IResource 
{ 
public: 
    virtual TKey GetKey() const = 0; 
protected: 
    static shared_ptr<TResource> Create(const TParams& params) 
    { 
     return ResourceManager::GetInstance().Load(params); 
    } 
    virtual Status Initialize(const TParams& params, const TKey key, shared_ptr<Viewer> pViewer) = 0; 
}; 

Create静态函数调用回模板ResourceManager类就是负责装载,卸载和存储实例它使用唯一密钥管理的资源类型,确保重复调用仅从商店中检索,而不是作为单独的资源重新加载。

template <typename TResource, typename TParams, typename TKey> 
class TResourceManager 
{ 
    sptr<TResource> Load(const TParams& params) { ... } 
}; 

具体的资源类从使用CRTP的IResource继承。专用于每种资源类型的ResourceManagers被声明为这些类的朋友,以便ResourceManager的Load函数可以调用具体资源的Initialize函数。一个这样的资源是质感一流,进一步使用PIMPL方法隐藏其私处:

class Texture2D : public IResource<Texture2D , Params::Texture2D , Key::Texture2D > 
{ 
    typedef TResourceManager<Texture2D , Params::Texture2D , Key::Texture2D > ResourceManager; 
    friend class ResourceManager; 

public: 
    virtual Key::Texture2D GetKey() const override final; 
    void GetWidth() const; 
private: 
    virtual Status Initialize(const Params::Texture2D & params, const Key::Texture2D key, shared_ptr<Texture2D > pTexture) override final; 

    struct Impl; 
    unique_ptr<Impl> m; 
}; 

我们大部分的纹理类的实现是平台无关的(如GetWidth功能,如果它只是返回一个int存储在Impl中)。但是,根据我们所针对的图形API(例如,Direct3D11与OpenGL 4.3),某些实现细节可能有所不同。一种解决方案可以是从IResource继承一个定义所有纹理的扩展公共接口的中间Texture2D类,然后从中继承一个D3DTexture2D和OGLTexture2D类。这个解决方案的第一个问题是,它要求您的API用户时刻关注他们所针对的图形API(他们可以在两个子类上调用Create)。这可以通过将Create限制为中间级的Texture2D类来解决,该类可能使用#ifdef开关来创建D3D或OGL子对象。但是这个解决方案仍然存在第二个问题,那就是独立于平台的代码会被复制到两个孩子身上,从而导致额外的维护工作。您可以尝试通过将与平台无关的代码移入中介类来解决此问题,但如果某些成员数据由平台特定的和平台无关的代码使用,会发生什么情况? D3D/OGL儿童将无法访问中介商Impl中的数据成员,因此您必须将他们从Impl中移出并放入标题中,以及他们携带的任何依赖关系,从而暴露包含您的标题的任何人到那些他们不需要知道的垃圾。

API应该很容易使用,并且很难使用错误。易于使用的一部分是将用户的暴露限制在他们应该使用的API的各个部分。这个解决方案打开它容易使用错误,并增加了维护开销。用户应该只关心他们在一个地方定位的图形API,而不是他们使用API​​的地方,他们不应该暴露于内部的依赖关系。这种情况尖叫了部分类,但它们在C++中不可用。相反,您可以简单地在单独的头文件中定义Impl结构,一个用于D3D,一个用于OGL,并将一个#ifdef开关置于Texture2D.cpp文件的顶部,并通用地定义其余的公共接口。这样,公共接口就可以访问它所需要的私有数据,唯一重复的代码是数据成员声明(构建仍然可以在创建Impl的Texture2D构造函数中完成),您的私有依赖项保持私有,用户不需要不必在意任何东西,除了使用一组有限的公开的API调用表面的:

// D3DTexture2DImpl.h 
#include "Texture2D.h" 
struct Texture2D::Impl 
{ 
    /* insert D3D-specific stuff here */ 
}; 

// OGLTexture2DImpl.h 
#include "Texture2D.h" 
struct Texture2D::Impl 
{ 
    /* insert OGL-specific stuff here */ 
}; 

// Texture2D.cpp 
#include "Texture2D.h" 

#ifdef USING_D3D 
#include "D3DTexture2DImpl.h" 
#else 
#include "OGLTexture2DImpl.h" 
#endif 

Key::Texture2D Texture2D::GetKey() const 
{ 
    return m->key; 
} 
// etc...