2016-11-30 5 views
-3

Я изучаю C++ (исходя из курсовой работы C и Java), и сегодня я хочу написать класс, который фильтрует байты, взятые из общего потока, и записывает его вывод в другой поток.C++ Лучший подход к фильтрации байтов в потоке

Чтобы быть совпадающим, допустим, что я хочу создать класс, который base64-кодирует вход и записывает вывод в stdout.

В Баш я хотел бы написать:

echo "some input data" | base64 

В C++ я хочу реализовать класс MyB64Encoder, который будет вести себя так:

std::cout << myB64EncoderObject << "some input data"; 

//Alternatively, is it possible to make it like this? 
std::cout << MyB64Encoder << "some input data"; 

Дело в том, то myB64EncoderObject имеет, конечно, для поддержания внутреннего состояния и внутреннего буфера. Чтобы предотвратить блокирование и чрезмерное использование памяти, он должен иметь возможность читать и обрабатывать небольшие фрагменты данных и выводить каждый из них сразу после его обработки.

Есть несколько вещей, чтобы заботиться о:

  • Объект должен ждать выходной поток, чтобы иметь возможность получать данные
  • Объект должен бросить ошибку, если нет чтения потока от него (вроде как сломанная труба?)

Какой был бы лучший подход к такой проблеме, с точки зрения эффективности? Как реализовать его в современном C++ 1x?

+1

Оберните ручей (или оберните ручейщик). –

ответ

0

Вы можете сделать что-то вроде этого:

class MyEncoder 
{ 
public: 
private: 
    std::ostream* os = nullptr; 

    // This overload deals with: 
    // std::cout << myEncoder ... 
    friend MyEncoder& operator<<(std::ostream& os, MyEncoder& me) 
    { 
     // grab a reference to the target output stream 
     me.os = &os; 
     return me; 
    } 

    // This overload deals with: 
    // std::cout << MyEncoder() ... 
    friend MyEncoder& operator<<(std::ostream& os, MyEncoder&& me) 
    { 
     // the temporary is currently bound to the l-value parameter me 
     // so we can just pass this call on to the previous overload 
     return os << me; 
    } 

    // This overload deals with: 
    // myEncoder << <anything else> 
    template<typename T> 
    friend MyEncoder& operator<<(MyEncoder& me, T const& v) 
    { 
     // only encode if there is an output stream to send the data to 
     // this will only be set if one of the above overloads was called 
     if(!me.os) 
      throw std::runtime_error("no stream to receive encoded data"); 

     // do your encoding here 
     (*me.os) << "{encoded: " << v << "}"; 

     return me; 
    } 
}; 

В основном для достижения этой цели:

std::cout << MyEncoder() << "some data: " << 45; 
//          ^calls operator<<(MyEncoder&, 45) 
//      ^calls operator<<(MyEncoder&, "some data: ") 
//  ^calls operator<<(std::cout, MyEncoder()) 

Звонки идут слева направо.

Это может показаться немного привлекательным, но в основном это покрытие 3 различных возможностей вызова.

MyEncoder encoder; 
std::cout << encoder; // MyEncoder& object 

std::cout << MyEncoder(); // (temporary) MyEncoder&& object 

encoder << "anything else" // A MyEncoder& receiving any other object 

Первые 2 операторы перегружены, чтобы установить внутренний std::ostream* и третий оператор перегружен, чтобы сделать фактическую кодировку.

1

Существующие вещи, которые ведут себя так:

std::cout << myB64EncoderObject << "some input data"; 

являются I/O manipulators (например станд :: boolalpha, станд :: шестигранная, ....). Однако они просто устанавливают флаги в потоке, которые он уже знает, как интерпретировать.

Если вы хотите сохранить этот синтаксис, вам нужно что-то более сложное, а именно промежуточный обертку:

class B64Wrapper { 
    std::ostream &os_; 
    B64Encoder &enc_; // only if your encoder is really stateful 

public: 
    B64Wrapper() = delete; 
    B64Wrapper(B64Wrapper&&) = default; 
    B64Wrapper(B64Wrapper const&) = default; 

    B64Wrapper(std::ostream &os, B64Encoder &enc) : os_(os), enc_(enc) {} 


    template <typename T> 
    B64Wrapper& operator<< (B64Wrapper &self, T val) { 
     self.enc_.encode(os_, val); 
     return self; 
    } 
}; 

B64Wrapper operator<< (std::ostream &os, B64Encoder &enc) { 
    return B64Wrapper(os, enc); 
} 

(заметим, вам все еще нужно написать метод B64Encoder::encode(std::ostream &, T value)).

Если ваш кодировщик не имеет особого состояния, вам не нужна ссылка на него и объявите B64Encoder как пустой тег с глобальным экземпляром, чтобы получить тот же эффект - в этом случае он существует только для выбора operator<< перегрузка.

Другой подход заключается в написании std::basic_streambuf осуществления, который кодирует входной сигнал на sputc/sputn/xsputn. Он может перенаправить все остальное на обернутый streambuf или на базовый класс, в зависимости от того, на что вы наследуете.