在C/C++程序中实现管道

3
我有以下问题:
我编写了一个简单的解压程序,用于解压由另一个进程按行处理的.gz文件。 因此,在shell中,我可以键入: unzipper file | program
这两个程序均为C / C ++编码。
是否有人知道如何在一个C / C ++程序中实现此“管道”(|),以便我可以像这样将其多线程化...
在我特定的情况下,保持换行符结构完整非常重要,这就是为什么我使用管道的原因。 .gz文件太大,无法作为整体保存在内存中。

你不需要使用管道逐行处理输入。只需逐行读取即可。也就是说,你可以通过使用线程或协程来简化控制结构(例如,据我所知,这在20世纪60年代的Algol编译器中已经实现了)。 - Cheers and hth. - Alf
答案取决于您的操作系统。我建议根据您的意思添加一个“linux”、“osx”或“windows”标签(或者可能是“solaris”或“posix”或其他)。 - Nemo
所以基本上你想把这两个程序合并成一个?你可以使用pipe系统调用来创建管道。一个线程解压文件并将其写入管道;另一个线程从管道中读取数据并执行相应操作。至于通过这种方式是否会得到任何好处,这是一个未知的问题。 - Duck
此外,这些行将以4个为一组进行处理... - Niels
你是指超过4行的任何多余字符吗?最简单的方法是,你处理完你的4行后,将剩余的字符移到缓冲区的开头,并继续处理。 - Duck
显示剩余5条评论
1个回答

1
在编程中,有一种称为生成器的东西;在C++中,我们倾向于将它们视为输入迭代器,但关注点仍然相同:就像管道一样,它是基于拉动驱动的生产。
因此,您可以围绕生产者(最好具有输入迭代器的接口)和消费者的想法重构程序,并且消费者将逐行请求输入,而生产者将懒洋洋地提供。
对于必要接口的良好指南,我建议使用受人尊敬的SGI STL网站:这里是InputIterator概念的链接。
对于一个更简单的例子,假设我们不必处理解压缩,只需按行读取文件:
class LineIterator: public std::iterator<std::input_iterator_tag,
                                         std::string const>
{
public:
    // Default Constructible
    LineIterator(): stream(nullptr) {}

    explicit LineIterator(std::istream& is): stream(&is) { this->advance(); }

    // Equality Comparable
    friend bool operator==(LineIterator const& left, LineIterator const& right) {
        return left.stream == right.stream
           and left.buffer == right.buffer
           and left.currentLine == right.currentLine;
    }

    friend bool operator!=(LineIterator const& left, LineIterator const& right) {
        return not (left == right);
    }

    // Trivial Iterator (non mutable)
    pointer operator->() const { return &currentLine; }

    reference operator*() const { return currentLine; }

    // Input Iterator
    LineIterator& operator++() {
        this->advance();
        return *this;
    } // operator++

    LineIterator operator++(int) {
        LineIterator tmp(*this);
        ++*this;
        return tmp;
    } // operator++

private:
    void advance() {
        // Advance a valid iterator to fetch the next line from the source stream.
        static LineIterator const SingularValue;

        assert(*this != SingularValue and "Cannot advance singular iterator");
        // Note: in real life, I would use std::getline...
        // ... but it would not showcase the double-buffering model
        // required to solve the OP problem (because of decoding)

        // We use double-buffering, so clear current and swap buffers
        currentLine.clear();
        swap(buffer, currentLine);

        // Check if we found some new line or not
        size_t const nl = currentLine.find('\n');

        // If we found one already, preserve what's after in the buffer
        // as we only want to expose one line worth of material.
        if (nl != std::string::npos) {
            if (nl == currentLine.size()) { return; } // nothing to preserve

            buffer.assign(currentLine.begin() + nl + 1, currentLine.end());
            currentLine.erase(currentLine.begin() + nl + 1, currentLine.end());
            return;
        }

        // If we did not, then we need to pump more data into the buffer.
        if (not stream) { return; } // Nothing to pump...

        static size_t const ReadBufferSize = 256;
        char input[ReadBufferSize];

        while (stream->read(input, ReadBufferSize)) {
            if (this->splitBuffer(input, ReadBufferSize)) { break; }
        }

        // We end up here either if we found a new line or if some read failed.
        // If the stream is still good, we successfully found a new line!
        if (*stream) { return; }

        // Otherwise, the stream is no good any longer (it dried up!)
        // but we may still have read some little things from it.
        this->splitBuffer(input, stream->gcount());

        stream = SingularValue.stream; // stream dried up,
                                       // so reset it to match singular value.
    } // advance

    bool splitBuffer(char const* input, size_t const size) {
        // Split input at the newline character, the first chunk ends
        // up in currentLine, the second chunk in buffer.
        // Returns true if a newline character was found, false otherwise.

        // Check if we finally found a new line
        char const* const newLine = std::find(input, input + size, '\n');

        // If we did not, copy everything into currentLine and signal it.
        if (newLine == input + size) {
            currentLine.append(input, size);
            return false;
        }

        // If we did, copy everything up to it (including it) into currentLine
        // and then bufferize the rest for the next iteration.
        currentLine.append(input, newLine + 1);
        buffer.assign(newLine + 1, input + size);
        return true;
    } // splitBuffer

    std::istream* stream;
    std::string buffer;

    std::string currentLine;
}; // class LineIterator

这可能有点啰嗦(而且可能存在漏洞...),但它具有我们需要与STL算法组合的接口,例如:

std::ifstream file("someFile.txt");
std::copy(LineIterator(file), LineIterator(), std::ostream_iterator(std::cout));

这将会逐行在终端上输出文件 (点此查看示例)。

现在,您需要用块读取和解压缩来替换获取部分 (stream.read) :)


好的,非常感谢您的回复。由于我并不是一个高级的C程序员,所以我有几个问题需要解答。能否有人解释一下以下的结构: - Niels
class LineIterator:public std::iterator<std::input_iterator_tag, std::string const>显式LineIterator(std::istream& is):stream(&is) { this->advance(); } - Niels
我不理解这个冒号的用法,那里发生了什么?然后,朋友是什么意思? - Niels
指针运算符->()和引用运算符*()分别有什么作用? - Niels
@Niels: 嗯,我不太确定我是否理解了所有的问题...让我试试看。1. std::iterator 是一个简单的类,没有方法/状态,只是声明了一些内部类型(比如 reference),这些类型是从迭代器中期望得到的;2. 在 Foo(int i): attribute(i) {} 中,位于 :{ 之间的部分是初始化列表,用于在构造函数体开始之前初始化类属性;3. friend 是一种声明允许一个类或函数访问当前类的protectedprivate部分的方式,而通常情况下是不允许的... - Matthieu M.
@Niels: ... 4. 当你写someclass->时,会调用operator->(),而当你写*someclass时,会调用operator*()(无参数),它们用于提供类似指针的接口。我建议你参考《C++权威指南和书单》来提升你对C++的了解,因为这些都是非常基础的问题,你很可能会反复遇到这些概念。 - Matthieu M.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接