任何人都知道是否可以在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。
这就是为什么我在寻找部分课程。我可以在编译时定义特定平台相关的部分结束,当然,在这个愚蠢的例子中,我仍然需要一个丑陋的#ifdef在genericMethod()中,但还有另一种方法可以避免这种情况。
答案 0 :(得分:35)
这在C ++中是不可能的,它会给你一个关于重新定义已经定义的类的错误。如果您想分享行为,请考虑继承。
答案 1 :(得分:17)
尝试继承
具体地
class AllPlatforms {
public:
int common();
};
然后
class PlatformA : public AllPlatforms {
public:
int specific();
};
答案 2 :(得分:14)
您无法在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)struction中创建一个非虚拟的调用开销,但如果内联的cont(de)结构被内联你应该没问题,并且这种开销在任何情况下都不会比使用被调用的genericInit方法更糟糕。两个平台。
客户端代码只是创建Test的实例,并且可以调用它们的方法,这些方法可以是genericTest,也可以是平台的正确版本。为了帮助代码中的类型安全而不关心平台并且不想意外地使用特定于平台的调用,您还可以执行以下操作:
#ifdef _WIN32
typedef genericTest<Win32Test> BaseTest;
#elif MAC
typedef genericTest<MacTest> BaseTest;
#endif
使用BaseTest时必须要小心,但与C ++中的基类一样,这一点并不多。例如,不要使用错误判断的传值来对其进行切片。并且不要直接实例化它,因为如果你这样做并调用最终尝试“假虚拟”调用的方法,那么你就麻烦了。后者可以通过确保所有genericTest的构造函数都受到保护来强制执行。
答案 3 :(得分:8)
#include will work as that is preprocessor stuff.
class Foo
{
#include "FooFile_Private.h"
}
////////
FooFile_Private.h:
private:
void DoSg();
答案 4 :(得分:8)
或者你可以试试PIMPL
通用头文件:
class Test
{
public:
...
void common();
...
private:
class TestImpl;
TestImpl* m_customImpl;
};
然后创建执行特定于平台的自定义实现的cpp文件。
答案 5 :(得分: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
是一个在编译时已知的类,因此抽象基类或其他任何内容都不会产生任何开销。
答案 6 :(得分:3)
对我而言,确定具有与平台无关的通用功能的多平台类似乎非常有用。
除了开发人员已经这样做了几十年没有这个'功能'。
我认为部分创建是因为微软几十年来一直养成生成代码并将其交给开发人员开发和维护的坏习惯。
生成的代码通常是维护的噩梦。当你需要碰撞你的MFC版本时,整个MFC生成框架的习惯是什么?或者在升级Visual Studio时如何在* .designer.cs文件中移植所有代码?
大多数其他平台更依赖于生成配置文件,而不是用户/开发人员可以修改。这些,词汇量有限,不易与不相关的代码混在一起。如果认为必要,配置文件甚至可以作为资源文件插入到二进制文件中。
我从未见过在继承或配置资源文件不能做得更好的地方使用'部分'。
答案 7 :(得分:2)
使用继承,如Jamie所说,或者#ifdef使不同的部分在不同的平台上编译。
答案 8 :(得分: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
答案 9 :(得分:1)
由于标题只是以文本方式插入,因此其中一个可以省略“class Test {”和“}”,并且#included在另一个的中间。
我实际上已经在生产代码中看到了这一点,尽管Delphi不是C ++。它特别恼火,因为它破坏了IDE的代码导航功能。
答案 10 :(得分:1)
不。
但是,您可能想要查找一种名为“策略类”的技术。基本上,你制作微类(它们本身没用)然后在稍后将它们粘合在一起。
答案 11 :(得分: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; }
}
可以提到很多其他原因......
答案 12 :(得分:1)
正如所写,这是不可能的,而且在某些情况下它实际上很烦人。
ISO 有一个官方提议,考虑到嵌入式软件,特别是为了避免继承和 pimpl 模式带来的 RAM 开销(这两种方法都需要为每个对象提供一个额外的指针):
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0309r0.pdf
不幸的是,该提案被拒绝了。
答案 13 :(得分:0)
您可以使用模板专业化和部分专业化来获得类似部分类的内容。在投入太多时间之前,请检查编译器对这些内容的支持。像MSC ++ 6.0这样的旧编译器不支持部分专业化。
答案 14 :(得分:0)
两次声明一个类主体可能会生成一个类型重定义错误。如果你正在寻找一个解决方案。我建议使用#ifdef'ing,或使用Abstract Base Class隐藏平台特定的详细信息。
答案 15 :(得分:0)
如上所述,这是不可能的。
您可能希望查看名称空间。您可以将函数添加到另一个文件中的命名空间。类的问题是每个.cpp都需要查看类的完整布局。
答案 16 :(得分:0)
如果要在不触及原始文件的情况下扩展类,则部分类很好。例如,我想用助手扩展std的矢量模板类。为什么我要创建像vectorEx这样的继承类而不仅仅是通过partial?
添加方法答案 17 :(得分:0)
让平台独立和平台相关的类/功能成为每个其他朋友类/功能。 :)
它们各自的名称标识符允许更好地控制实例化,因此耦合更宽松。部分打破OO的封装基础太绝对了,而必要的朋友声明几乎没有放松它,足以促进多范式分离关注点,例如特定于平台的平台独立的平台特定方面。
答案 18 :(得分: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继承。专门针对每种资源类型的ResourceManage声明为这些类的朋友,因此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
函数,如果它只返回存储在Impl中的int)。但是,根据我们所针对的图形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...
答案 19 :(得分:0)
假设我有:
MyClass_Part1.hpp,MyClass_Part2.hpp和MyClass_Part3.hpp
理论上,某人可以开发一个GUI工具,该工具可以读取上面的所有这些hpp文件并创建以下hpp文件:
MyClass.hpp
class MyClass
{
#include <MyClass_Part1.hpp>
#include <MyClass_Part2.hpp>
#include <MyClass_Part3.hpp>
};
用户可以从理论上告诉GUI工具每个输入hpp文件在哪里以及在何处创建输出hpp文件。
当然,开发人员可以从理论上对GUI工具进行编程,使其可以处理任意数量的hpp文件(不一定是3个),其前缀可以是任意字符串(不一定是“ MyClass”) )。
请不要忘记#include <MyClass.hpp>
在您的项目中使用类“ MyClass”。