C++ 最佳容器类型

C++ 最佳容器类型,c++,containers,stdvector,deque,C++,Containers,Stdvector,Deque,我正在开发一个需要“滑动窗口”的应用程序,它只是一个字符串容器 Window = ["the", "dog", "is", "hungry"] 应用程序处理大型文本文件,当满足某些条件时,窗口会在末尾添加一个新字符串并删除第一个元素 例如,说“现在”是下一个要添加的单词,然后 Window <- Window.AddToEnd("now") and Window.DeleteFirst() 窗口您可以使用 这允许您保留所需的位置,然后只移动值。除了std::deque,根据 提供类似

我正在开发一个需要“滑动窗口”的应用程序,它只是一个字符串容器

Window  = ["the", "dog", "is", "hungry"]
应用程序处理大型文本文件,当满足某些条件时,窗口会在末尾添加一个新字符串并删除第一个元素

例如,说“现在”是下一个要添加的单词,然后

Window <- Window.AddToEnd("now") and Window.DeleteFirst()
窗口您可以使用


这允许您保留所需的位置,然后只移动值。

除了
std::deque
,根据

提供类似于向量的功能,但在序列开始时,不仅在序列结束时,还可以有效地插入和删除元素。但是,与向量不同,DEQUE不能保证将其所有元素存储在连续的存储位置

您还可以使用固定大小的向量,并保存起始索引

所以它就像一个圆形阵列。e、 g:


初始为[A,b,c,d]且起始索引为0的向量v;在“e”出现之后,它是[e,b,c,d],但现在开始索引为1。第i个元素是
v[(开始索引+i)%v.size()]
。此方案很容易自己实现。

下面是一个循环容器的类模板示例,该容器的大小在运行时保持不变,但在类模板的实例化过程中设置。要使用该方法,必须提前知道或预先计算尺寸;如果你不知道大小会是什么,那么你可以考虑使用类似的算法,没有模板或其他容器。如果尺寸不是非常大,那么这确实非常有效。正如您在
addString()
函数中看到的,当添加的元素开始超过所包含数组的大小时,会调用一个for循环,该循环必须移动该数组中的所有元素。对于100或1000个元素的数组来说,这是很好的,但是当数组的大小为100000或1000000个条目时;这将成为瓶颈,而且速度非常慢,但是这确实提供了将所有内容向左移动一个空格并在数组、列表或容器末尾添加的机制

#include <iostream>
#include <memory>
#include <string>


template<unsigned Size>
class CircularContainer {
public:
    const static unsigned SIZE = Size;
private:
    std::string data_[SIZE];
    unsigned counter_;

public:
    CircularContainer() : counter_(0) {}

    void addString( const std::string& str ) {
        // In a real container this would be a member and not static
        // If you have a static here, and you have multiple instances
        // It will still increment across all instances.
        //static unsigned counter = 0;

        if ( counter_ < SIZE ) {
            data_[counter_++ % SIZE] = str;
        } else {
            // This function can be expensive on large data sets
            // due to this for loop but for small structures this
            // is perfectly fine.
            for ( unsigned u = 0; u < SIZE-1; u++ ) {
                data_[u] = data_[u+1];
            }
            data_[SIZE - 1] = str;
        }
    }

    std::string& getString( unsigned idx ) {
        if ( idx < 0 || idx >= SIZE ) {
            return std::string();
        } else {
            return data_[idx];
        }
    }

    unsigned size() const {
        return SIZE;
    }
};

int main() {

    CircularContainer<4> cc;

    cc.addString( "hello" );
    cc.addString( "world" );
    cc.addString( "how" );
    cc.addString( "are" );

    for ( unsigned u = 0; u < cc.size(); u++ ) {
        std::cout << cc.getString( u ) << "\n";
    }
    std::cout << std::endl;

    cc.addString( "you" );
    cc.addString( "today" );

    for ( unsigned u = 0; u < cc.size(); u++ ) {
        std::cout << cc.getString( u ) << "\n";
    }


    std::cout << "\nPress any key and enter to quit." << std::endl;
    char c;
    std::cin >> c;

    return 0;
}

它仍然使用指定的数组,并使用相同的技术围绕该数组的索引进行循环。这里唯一的区别是我使用的是
std::shared\u ptr
数组。这将导致存储的对象位于类模板作用域的堆上,而不是本地堆栈上。清理内存应该自动完成,但并不总是保证。除非您明确需要它们,否则不需要跟踪
头部
&
尾部
位置,这应该不难添加到此类模板中

您也可以只使用一个环形缓冲区(虽然标准库中没有现成的实现,但它们很容易实现)。好的,列表或循环缓冲区都可以满足您的需要。我建议从列表开始。对于这样一个广泛的问题,循环缓冲区是最好的测试版。我不认为传染性内存容器最适合这个问题。因此,
std::vector
不是一个好的选择。您的
std::vector
版本可以工作,但不是最理想的。您可能只需要执行strs.erase(strs.begin())
。至少你应该移动元素。第二种解决方案非常糟糕,不应该使用,这是很多不必要的复制。@Frank我刚刚添加了
std::move
调用。这有用吗?@Jarod42我知道;但是为了简单起见,我这样做了,因为它会递增,并且它的模和大小总是围绕索引,在这个演示中,我只想将它初始化为0一次。
addString
中的
static
计数器是错误的。当存在多个实例时,它们将错误地使用同一计数器。此外,它不是循环缓冲区。你的答案和下面的@Charles没有什么区别。@scinart多个例子都是这样,我可以在原始答案中记下这一点
#include <vector>
#include <string>

int main() {

    std::vector<std::string> strs;

    // ...

    std::string tmp;
    while (true) {
        for (size_t i = 1; i < strs.size(); ++i)
            strs[i-1] = std::move(strs[i]);
     // strs.erase(strs.begin()); also works.

        std::cin >> tmp;
        strs.back() = tmp;
    }

    return 0;
}
#include <iostream>
#include <memory>
#include <string>


template<unsigned Size>
class CircularContainer {
public:
    const static unsigned SIZE = Size;
private:
    std::string data_[SIZE];
    unsigned counter_;

public:
    CircularContainer() : counter_(0) {}

    void addString( const std::string& str ) {
        // In a real container this would be a member and not static
        // If you have a static here, and you have multiple instances
        // It will still increment across all instances.
        //static unsigned counter = 0;

        if ( counter_ < SIZE ) {
            data_[counter_++ % SIZE] = str;
        } else {
            // This function can be expensive on large data sets
            // due to this for loop but for small structures this
            // is perfectly fine.
            for ( unsigned u = 0; u < SIZE-1; u++ ) {
                data_[u] = data_[u+1];
            }
            data_[SIZE - 1] = str;
        }
    }

    std::string& getString( unsigned idx ) {
        if ( idx < 0 || idx >= SIZE ) {
            return std::string();
        } else {
            return data_[idx];
        }
    }

    unsigned size() const {
        return SIZE;
    }
};

int main() {

    CircularContainer<4> cc;

    cc.addString( "hello" );
    cc.addString( "world" );
    cc.addString( "how" );
    cc.addString( "are" );

    for ( unsigned u = 0; u < cc.size(); u++ ) {
        std::cout << cc.getString( u ) << "\n";
    }
    std::cout << std::endl;

    cc.addString( "you" );
    cc.addString( "today" );

    for ( unsigned u = 0; u < cc.size(); u++ ) {
        std::cout << cc.getString( u ) << "\n";
    }


    std::cout << "\nPress any key and enter to quit." << std::endl;
    char c;
    std::cin >> c;

    return 0;
}
#include <iostream>
#include <memory>
#include <string>

template<typename T, unsigned Size>
class CircularBuffer {
public:
    const static unsigned SIZE = Size;
private:
    std::shared_ptr<T> data_[SIZE];
    unsigned counter_;
public:
    CircularBuffer() : counter_(0) {}
    ~CircularBuffer() {}

    void addItem( const T& t ) {

        if ( counter_ < SIZE ) {
            data_[counter_++ % SIZE] = std::make_shared<T>( t );
        } else {
            for ( unsigned u = 0; u < SIZE - 1; u++ ) {
                data_[u] = data_[u + 1];
            }
            data_[SIZE - 1] = std::make_shared<T>( t );
        }
    }

    T getItem( unsigned idx ) {
        if ( idx < 0 || idx >= SIZE ) {
            throw std::exception( "Array Buffer Out of Bounds!" );
        } else {
            return *(data_[idx].get());
        }
    }

    unsigned size() const {
        return SIZE;
    }
};

int main() {
    CircularBuffer<std::string, 5> cb;
    cb.addItem( "hello" );
    cb.addItem( "world" );
    cb.addItem( "how" );
    cb.addItem( "are" );
    cb.addItem( "you" );

    for ( unsigned u = 0; u < cb.size(); u++ ) {
        std::cout << cb.getItem( u ) << "\n";
    }
    std::cout << std::endl;

    cb.addItem( "today" );
    cb.addItem( "my" );
    cb.addItem( "friend" );

    for ( unsigned u = 0; u < cb.size(); u++ ) {
        std::cout << cb.getItem( u ) << "\n";
    }
    std::cout << std::endl;


    std::cout << "\nPress any key and enter to quit." << std::endl;
    char c;
    std::cin >> c;

    return 0;
}