2011-08-24 13 views
7

En un programa C++, tenemos 3 flujos: stdin, stdout y stderr. ¿Puedo anularlos en una aplicación de consola y usarlos en una aplicación que usa formularios?Anular las transmisiones de C++

Por ejemplo, si en alguna clase base, tengo cout<< "...", ¿puedo "redirigir" a algo visual (como Windows Form)?

+2

C++ no tiene "formas". –

+3

Ok, me refiero a algo visual, no a la consola – Bakudan

+0

Tenga en cuenta que, por lo general, la anulación del comportamiento de las secuencias se realiza mediante la implementación de la interfaz std :: streambuf. Esa es una tarea bastante compleja, por lo que probablemente debería usar otras respuestas proporcionadas. – Basilevs

Respuesta

9

Lo que yo recomendaría hacer es tener una clase que se envuelve alrededor de un iostream como esto:

#include <iostream> 
#define LOG Log() 

class Log 
{ 
    public: 
     Log(){} 
     ~Log() 
     { 
     // Add an newline. 
     std::cout << std::endl; 
     } 


     template<typename T> 
     Log &operator << (const T &t) 
     { 
     std::cout << t; 
     return * this; 
     } 
}; 

Entonces, cada vez que desee cambiar el lugar donde van los datos, sólo cambia el comportamiento de la clase. Aquí es cómo se utiliza la clase:

LOG << "Use this like an iostream."; 

[editar] Como sugirió matamoscas papa, voy a añadir un ejemplo de algo que no sea cout:

#include <sstream> 
#define LOG Log() 

// An example with a string stream. 
class Log 
{ 
    private: 
     static std::stringstream buf; 
    public: 
     Log(){} 
     ~Log() 
     { 
     // Add an newline. 
     buf << std::endl; 
     } 


     template<typename T> 
     Log &operator << (const T &t) 
     { 
     buf << t; 
     return * this; 
     } 
}; 

// Define the static member, somewhere in an implementation file. 
std::stringstream Log::buf; 

cuanto a por qué usted debe tratar esto en lugar de heredar de algo así como un flujo de cadena, principalmente porque puedes cambiar fácilmente a donde el Logger saca dinámicamente. Por ejemplo, usted podría tener tres corrientes de salida diferentes, y utilizar una variable miembro estática de cambiar entre en tiempo de ejecución:

class Log 
{ 
    private: 
     static int outputIndex = 0; 
     // Add a few static streams in here. 
     static std::stringstream bufOne; 
     static std::stringstream bufTwo; 
     static std::stringstream bufThree; 
    public: 
     // Constructor/ destructor goes here. 

     template<typename T> 
     Log &operator << (const T &t) 
     { 
     // Switch between different outputs. 
     switch (outputIndex) 
     { 
      case 1: 
       bufOne << t; 
       break; 
      case 2: 
       bufTwo << t; 
      case 3: 
       bufThree << t; 
      default: 
       std::cout << t; 
       break; 
     } 
     return * this; 
     } 

     static void setOutputIndex(int _outputIndex) 
     { 
      outputIndex = _outputIndex; 
     } 
}; 

// In use 
LOG << "Print to stream 1"; 
Log::setOutputIndex(2); 
LOG << "Print to stream 2"; 
Log::setOutputIndex(3); 
LOG << "Print to stream 3"; 
Log::setOutputIndex(0); 
LOG << "Print to cout"; 

Esto puede ampliar fácilmente para crear una poderosa manera de hacer frente a la tala. Se podría añadir FileStreams, utiliza std :: cerr, etc.

+1

Acepto (en principio). –

+1

¿No sería más fácil simplemente crear una nueva clase derivada de ostream y usar eso? –

+0

Esto básicamente comienza desde cero. ¿Qué sucede si, por ejemplo, el código existente usa manipuladores de formato o 'endl'? Además, este ejemplo no ilustra cómo interactuar con algo además de 'cout'. – Potatoswatter

1

La forma habitual de poner una interfaz iostream en otra cosa es utilizar las clases stringstream/istringstream/ostringstream en <sstream>.

La mejor manera es cambiar el uso de cin, cout, etc para parámetros de tipo istream & y ostream &. Si eso es realmente imposible, puede modificar cin.rdbuf() para apuntar a la secuencia que desea modificar, pero eso es extremadamente hackish y no funcionará con multihilo.

Copie el texto del formulario en un istringstream, luego páselo al código existente como istream &. Cuando termine, lea los resultados del stringstream pasado como ostream & que reemplazó cout y lo copió en el formulario.

No declare argumentos del tipo istringstream & o . Usa las clases base.

Si realmente lo desea, puede crear la subclase std::basic_stringbuf<char> y anular las funciones virtuales sync y underflow para vincular las transmisiones directamente a los campos de texto. Pero eso es bastante avanzado y probablemente no valga la pena.

+1

No modifica 'cin.rdbuf()', lo llama. Y no es "hackish", es exactamente cómo los iostreams de la biblioteca estándar están diseñados para ser utilizados. –

+0

@Ben: me refiero a modificar el puntero del búfer subyacente de la variable global 'cin' para apuntar a una variable local, de modo que una función llamada pueda acceder al local a través de un global. Eso es muy hackish. – Potatoswatter

+0

Una vez más, los iostreams estándar están diseñados para reemplazar sus objetos 'streambuf', y proporcionan API públicas para hacerlo de manera limpia. Así es como se implementa el polimorfismo en la biblioteca estándar iostreams. –

2

Aquí está el código que utilizo para redirigir std::cout a una interfaz gráfica de usuario en Windows:

struct STDOUT_BLOCK : SLIST_ENTRY 
{ 
    char sz[]; 
}; 

class capturebuf : public std::stringbuf 
{ 
protected: 
    virtual int sync() 
    { 
     if (g_threadUI && g_hwndProgressDialog) { 
      // ensure NUL termination 
      overflow(0); 
      // allocate space 
      STDOUT_BLOCK* pBlock = (STDOUT_BLOCK*)_aligned_malloc(sizeof *pBlock + pptr() - pbase(), MEMORY_ALLOCATION_ALIGNMENT); 
      // copy buffer into string 
      strcpy(pBlock->sz, pbase()); 
      // clear buffer 
      str(std::string()); 
      // queue string 
      ::InterlockedPushEntrySList(g_slistStdout, pBlock); 
      // kick to log window 
      ::PostMessageA(g_hwndProgressDialog, WM_APP, 0, 0); 
     } 
     return __super::sync(); 
    } 
}; 

A continuación, en el interior main():

capturebuf altout; 
std::cout.set_rdbuf(&altout); 

Por supuesto, este caso es necesario para manejar el mensaje WM_APP en su ventana procedimiento y tire de las cuerdas del SList. Pero esto maneja la parte de redirección cout.

Como jweyrich toma nota correctamente, debe cambiar el streambuf* antes de que altout salga del alcance. Este código lo hará:

struct scoped_cout_streambuf_association 
{ 
    std::streambuf* orig; 
    scoped_cout_streambuf_association(std::streambuf& buf) 
     : orig(std::cout.rdbuf()) 
    { 
     std::cout.rdbuf(&buf); 
    } 

    ~scoped_cout_streambuf_association() 
    { 
     std::cout.rdbuf(orig); 
    } 
}; 

Y dentro main:

capturebuf altout; 
scoped_cout_streambuf_association redirect(altout); 
+0

No te olvides de restaurar el rdbuf original antes de salir, de lo contrario podría bloquearse. – jweyrich

+0

No hay función 'set_rdbuf', solo dos sobrecargas de' ios :: rdbuf'. – Potatoswatter

+0

@Potatoswatter: existe en Visual C++, y este código es específico de Windows, aunque la idea general es bastante portable, los detalles no lo son. –

Cuestiones relacionadas