从接口类返回std容器是不好的做法?

时间:2014-07-16 04:49:27

标签: c++

我遇到了这样一个问题。

我需要设计一个接口类,看起来像是

struct IIDs
{
    ....
    const std::set<int>& getAllIDs() = 0;  //!< I want the collection of int to be sorted.

}

void foo()
{
    const std::set<int>& ids = pIIDs->getAllIDs();
    for(std::set<int>::const_iterator it = ids.begin();....;..) {
         // do something
    }
}

我认为返回std的容器有点不合适,因为它会强制实现使用std :: set来存储ID的值,但如果我把它写成如下:

struct IIDs
{
    ....
    int count() const = 0;
    int at(int index) = 0;    //!< the itmes should be sorted
}

void foo()
{
   for (int i = 0; i < pIIDs->count(); ++i) {
       int val = pIIDs->at(u);
       ...
   }
}

我发现std的容器都没有提供这些请求:

  1. 索引查找的复杂性需要小于或等于O(log n)。
  2. 插入的复杂性需要小于或等于O(log n)。
  3. 必须对物品进行分类。
  4. 所以我只需要使用示例.1,这些是否可以接受?

4 个答案:

答案 0 :(得分:3)

通常不应跨DLL边界使用STL容器和模板代码。

返回复杂类型(如STL容器)时必须记住的是,如果您的调用跨越了运行不同内存管理器的两个不同DLL(或DLL和应用程序)之间的边界,您的应用程序很可能会崩溃。

构成STL代码的模板将在实现DLL中执行,从而创建容器所使用的所有内存。稍后当它在您的调用代码中留下范围时,您自己的内存管理器将尝试释放它不拥有的内存,从而导致崩溃。

如果您知道您的代码不会跨越D​​LL边界,并且只会在单个内存管理器的上下文中调用,那么就内存管理而言,您就可以了。

但是,即使在您只返回引用的情况下,例如上面的示例,容器的生命周期将完全由接口实现代码管理,除非您知道完全相同的版本STL和完全相同的编译器和链接器设置用于编译作为调用者的实现,您要求麻烦。

答案 1 :(得分:1)

我看到的问题是你是通过const引用返回集合,这意味着你有一个该集合类型的成员并返回对它的引用,如果你将一个局部变量返回给该函数(无效的内存访问问题)。

如果它是一个成员变量,那么最好提供对开始和结束迭代器的访问。如果是局部变量,则可以按值返回(C ++ 11应该优化并且不复制任何内容)。如果它的DLL边界尝试所有意味着不使用任何C ++类型,只使用C类型。

答案 2 :(得分:1)

在设计和良好的通用代码方面,更喜欢STL方式:返回迭代器,让容器类型为IIDs的实现细节,并使用typdef s

struct IIDs
{
    typedef std::set<int> Container;
    typedef Container::iterator IDIterator;

    // We only expose iterators to the data
    IDIterator begin();  //!< I want the collection of int to be sorted.
    IDIterator end(); 
    // ... 
};

答案 3 :(得分:0)

有各种方法:

  • 如果您希望最小化IIDs实现上客户端代码的耦合并确保在IIDs对象仍然存在时完成迭代,则使用访问者模式:调用代码必须提供一些函数依次为每个成员元素调用,并且不负责迭代本身

访客示例:

struct IIDs
{
    template <typename T>
    void visit(T& t)
    {
        for (int i : ids_) t(i);
    }
    ...
  private:
    std::set<int> ids_;
};
  • 如果你想让调用者更自由地将其他代码与容器遍历混合在一起,并且有多个并发独立遍历,那么提供迭代器,但要注意客户端代码可以使迭代器保持比{更长的时间。 {1}}对象本身 - 您可能或可能不想优雅地处理该场景