2011-12-29 61 views
1

我正在研究为C++ STL容器向量构建包装器(将有更多的功能,然后向量可以提供(加载,保存等),所以它需要是一个包装) 。构建STL向量的接口

我的包装的消费者需要迭代通过元素,如果我公开了STL迭代器,稍后更改实现将需要我的调用者重新编译(加上我觉得我打破封装)。

我想创建一个只返回原始类型的接口,以确保客户端不需要重新编译,如果我改变实现。我正在考虑将矢量大小公开为一个整数(类似于MFC CArray的工作方式),并且还重载了[]运算符,调用者可以用这种方式循环矢量。

我的问题:

  1. 如果我想返回一个int的矢量大小请问是怎么工作,SIZE_TYPE? Size_type看起来不像应该在接口中公开,因为如果它改变了,调用者将需要重新编译。如果size_type可能大于整数(我不希望我会拥有那么多元素!),我会很乐意施加某种限制。
  2. 使用[]运算符循环矢量显著差那么使用迭代器

编辑:删除单词“通用” - 这已无关的模板,它只是一个类。还澄清“公开原始类型”是指返回一个int而不是数据成员本身的方法。

+2

使用迭代器和'size_type'比使用整数索引和'operator []'更具有通用性。迭代器抽象了容器的类型,'size_type'允许size值是任何整型。 – 2011-12-29 06:04:48

+0

如果稍后更改实施以使用Boost等类型,那么使用我的STL迭代器的客户端将需要重新编译它们吗? – TownCube 2011-12-29 06:08:54

+1

你可以为迭代器编写自己的包装器,就像使用vector一样。但是,是的,如果'size_type'的类型发生了变化,那么它们将不得不重新编译。 – 2011-12-29 06:16:56

回答

2

您可以定义一个singelton IFC(即您的客户将引用纯虚)类。我认为这种设计模式被称为“Singelton工厂方法”。 我希望我的长答案能帮助你:)。

只要你不改变公共接口(方法列表),如果你改变你的代码,你的客户就不需要重新编译。

类似:

myClassIfc.h:

Class myClassIfc 
{ 
public: 
    virtual ~myClassIfc();  

    ///// list all your pure virtual public ifc methods here //// 
    void m_zRunMyMethod(int nNumber) = 0; 
    int m_nSize() = 0; 

    static myClassIfc* ms_pGetImplObj();  

protected:   
    myClassIfc(); 
    static myClassIfc* ms_pImplObj; 
} 

inline myClassIfc* myClassIfc::ms_pGetImplObj() 
{ 
    return ms_pImplObj; 
} 

myClassIfc.cpp:

#include myClassIfc.h 

myClassIfc::myClassIfc() 
{ 
} 
myClassIfc::~myClassIfc() 
{ 
} 

myClass.h - 实现你的纯虚类

Class myClass: public myClassIfc 
{ 
public: 
    virtual ~myClass();  

    void m_zRunMyMethod(int nNumber); 
    int m_nSize(); 

    static void ms_zCreate(); 
    static void ms_zDestroy(); 

protected:   
    myClass(); 

private: 
    vector<int> myInternalVector; 
} 

MyClass的.cpp:

#include myClass.h 

void myClass::m_zRunMyMethod(int nNumber) 
{ 
    /// your action 
    printf("%d\n", nNumber); 
} 

int myClass::m_nSize() 
{ 
    return int(myInternalVector.size()); 
} 

void myClass::ms_zCreate() 
{ 
    if (NULL != ms_pImplObj) 
    { 
     return; 
    } 
    ms_pImplObj = (myClass*) new myClass(); 
} 

void myClass::ms_zDestroy() 
{ 
    if (NULL == ms_pImplObj) 
    { 
     return; 
    } 
    delete ms_pImplObj; 
    ms_pImplObj = NULL; 
} 

现在上述长期基础性工作后,你的客户端需要使用

#include myClassIfc.h 


void main(void) 
{ 
    myClassIfc::ms_pGetImplObj()->m_zRunMyMethod(5); 
    myClassIfc::ms_pGetImplObj()->m_nSize();   
} 

我没有上面列出的唯一的事情就是你的内存管理,这意味着谁创造的单对象本身(调用派生类的ms_zCreate()静态API)。您可以从其他地方或直接从您的代码中调用它。

通知您可以操纵上述IFC方法是非singelton实施。 只要ifc类不更改,如果您修改派生(实现)类,则客户端代码将不需要重新编译。

+0

看起来像一个伟大的工作。 – 2011-12-29 09:13:16

+0

更好的方法是使用pimpl并转发,而不是使用继承来试图隐藏实现细节。 – 2011-12-29 14:56:19

1

我不建议实际使用索引和运算符[]。不要直接暴露向量迭代器,而要在您的类中创建迭代器类型定义,它最初与向量迭代器相同,但您可以将其更改为任何想要的内容,而无需客户端更改代码。然后你可以使用正常的开始/结束/查找方法。

+0

我知道他们不需要更改代码,但是如果typedef更改,他们是否需要重新编译? – TownCube 2011-12-29 06:32:03

+0

是的。可能避免重新编译的唯一方法是创建自己相应的迭代器包装,并将整个实现隐藏在源文件中。请记住,ABI破坏(并因此强制重新编译)在C++中很难避免。 – 2011-12-29 14:45:58

1

有很多你在问现代C++设计中的苍蝇。我会把你推荐给赫伯斯特特和安德烈亚历山德斯库的书C++ Coding Standards。查看课程设计中的这些章节标题:

Class Design and Inheritance 
32. Be clear what kind of class you’re writing. 56 
33. Prefer minimal classes to monolithic classes. 57 
34. Prefer composition to inheritance. 58 
35. Avoid inheriting from classes that were not designed to be base classes. 60 
38. Practice safe overriding. 66 
39. Consider making virtual functions nonpublic, and public functions nonvirtual. 68 
41. Make data members private, except in behaviorless aggregates (C-style structs). 72 
42. Don’t give away your internals. 74 
44. Prefer writing nonmember nonfriend functions. 79 

要特别注意#44。而不是重写STL向量,创建操作STL迭代器的非成员函数。这也与#35相关。 STL类不是真的被设计为基类。 #33是我对你的评论感到畏缩的原因,你想添加“加载”和“保存”功能到STL向量。这些功能听起来像是非会员功能。

哦,你真的以为这通过:

我想建立一个只暴露原始类型,以确保客户将不需要重新编译,如果我改变实现的接口。我正在考虑将矢量大小公开为整数

一方面,您希望使用合成将矢量隐藏到另一个类中。好的。不是最灵活的设计,但可能需要与非C++代码进行交互。但是,您想要将类的数据成员公开为接口的一部分。这可能会造成客户需要重新编译的问题。这没有意义。要么你想要全封装,要么不关心频繁的重新编译。这是什么? (并且请不要在MFC CArray类中建模任何东西。这是可怕的。)

+0

感谢您的回答。我不必公开大小,这只是我认为可以让客户使用[]运算符来遍历数组的迭代方式之一。我正在寻找一种方法,它将返回一个表示数组大小的整数(而不是暴露数据成员),因为从查看各种API并且它们的入口点重新调整int似乎是安全和常见的。 – TownCube 2011-12-29 10:29:28

+0

+1用于非成员,非好友算法。 – 2011-12-29 14:57:43

+0

@Cube,STL Vector已经有了这样一个方法:size()。它是一个返回size_type的const方法,通常定义为size_t,它通常是一个无符号整数。 – jmucchiello 2011-12-29 21:54:04

0

你说“我想建立一个只露出 原始类型,以确保客户将不需要重新编译的接口。”只是 相反是真的;如果要避免重新编译,则需要使用编译防火墙惯用语将所有内容全部设置为用户定义的类型。 在迭代的情况下,这可能有一个不可接受的性能 处罚:迭代成语需要深拷贝,而事实上, 没什么可内联很可能影响到优化为好。

我想避免重新编译完全可能是不合理的,因为 的C++的方式工作。更重要的是,如果您更改实现,则您希望避免在客户端代码中修改 。例如,在这个 的情况下,您要确切地定义您想要保证的内容;如果你决定迭代器进入你的类必须是随机的 访问迭代器,那么到std::vector<>::iterator的typedef是 可能就足够了;如果你想要保证的只是前向迭代 (为了让实现更加自由),你想要 考虑将std::vector<>::iterator包装在你自己的迭代器中, 只会公开你愿意保证 支持的操作所有未来的版本。

请注意,如果您稍后决定使用不支持随机访问迭代器的实现,则不能支持[]也不支持[]。 与仅支持前向迭代器的 相比,支持[]对未来的实现施加了更多约束。