2011-08-30 13 views
6

que tienen la siguiente interfaz de clase:¿Cómo funciona "en cascada"?

class Time 
    { 
    public: 
    Time(int = 0, int = 0, int = 0); 
    Time &setHour(int);     
    Time &setMinute(int);    
    Time &setSecond(int); 

    private: 
    int hour; 
    int minute; 
    int second; 
    }; 

La aplicación está aquí:

Time &Time::setHour(int h) 
    { 
    hour = (h >= 0 && h < 24) ? h : 0; 
    return *this; 
    } 


    Time &Time::setMinute(int m) 
    { 
    minute = (m >= 0 && m < 60) ? m : 0; 
    return *this; 
    } 


    Time &Time::setSecond(int s) 
    { 
    second = (s >= 0 && s < 60) ? s : 0; 
    return *this; 
    } 

En mi archivo .cpp principal, tengo este código:

int main() 
{ 
    Time t;  
    t.setHour(18).setMinute(30).setSecond(22); 
    return 0; 
} 

¿Cómo es posible encadenar estas llamadas de función juntas? No entiendo por qué esto funciona.

Respuesta

5

Cada uno de los métodos de t devuelve una referencia a t. Una referencia es un alias. Así que si lo hizo

Time t; 
Time& tAgain = t; 
tAgain.setMinute(30); 

tAgain.setMinute también altera el tiempo de t.

Ahora extrapole ese ejemplo simple a la cascada. Cada método de t devuelve una referencia a sí mismo

Time &Time::setSecond(int s) 
    { 
     second = (s >= 0 && s < 60) ? s : 0; 
     return *this; 
    } 

Así que en la expresión:

t.setHour(18).setMinute(30) 

t.setHour(18) llamadas sethour en t, a continuación, devuelve una referencia a t. En este caso, la referencia es temporal. Así que usted puede pensar en ella como si la línea superior cambia a la siguiente en la evaluación de sethour:

tAgain.setMinute(30); 

t.setHour devuelve una referencia - similar a nuestro tAgain anteriormente. Solo un alias para t.

5

Debido a que cada función devuelve una referencia al objeto this object (The return * this).

Básicamente esto significa que cada vez que se llama a la función realiza los cambios pertinentes y luego pasa todo el objeto de nuevo como referencia. Entonces es posible hacer llamadas en ese objeto devuelto.

También se puede escribir de la siguiente manera:

Time t; 
Time& t1 = t.setHour(18); // t1 will refer to the same object as t. 
Time& t2 = t1.setMinute(30); // t2 will refer to the same object as t1 and t. 
Time& t3 = t2.setSecond(22); // t3 will refer to the same object as t2, t1 and t. 

Eso puede hacer que sea más fácil de entender lo que está pasando.

+0

oh lo tengo .. 't.setHour (18)' saldrá '(* this)' que se usará para referirse a la próxima función ... – teacher

+1

@teacher: Exactamente. – Goz

14

La razón de que esto funciona correctamente, es que cuando se llama a

t.setHour(18) 

El valor de retorno es un Time&, una referencia a un objeto Time. Más importante aún, se define como

Time &Time::setHour(int h) 
{ 
    hour = (h >= 0 && h < 24) ? h : 0; 
    return *this; // <--- right here 
} 

Dentro de una función miembro, this es un puntero al objeto sobre el que se realizó la llamada, y *this es una referencia al objeto sobre el que se realizó la llamada (el objeto receptor). Esto significa que cuando llama al setHour, la función establece la hora en el tiempo, luego devuelve una referencia al objeto Time en el que hizo la llamada. Por lo tanto, t.setHour(18) establece la hora y luego devuelve una referencia al objeto receptor. De esta manera, se puede escribir

t.setHour(18).setMinute(30).setSecond(22); 

porque se interpreta como

((t.setHour(18)).setMinute(30)).setSecond(22); 

y en cada caso la función devuelve una referencia a t.

Más generalmente, cada vez que una función devuelve una referencia y esa referencia es *this, cualquier operación que realice en el valor de retorno de la función es indistinguible de las operaciones que realizaría en el objeto mismo.

Espero que esto ayude!

3

Esto es similar a los operadores de flujo de sobrecarga.

ostream& operator<<(ostream& s, const T& val) 
{ 
    s << val; 
    return s; 
} 

Lo hace porque modifica la secuencia y la devuelve para que se pueda utilizar en la siguiente llamada en cascada, si lo desea. Se sigue pasando por referencia para que pueda seguir en el siguiente segmento de expresión.

esa es la forma:

std::cerr << 1 << 2 << 3 << std::endl; 

funciona! :)

+0

Esto se parece más a un comentario – sehe

1

La técnica se llama method chaining. En el ejemplo que ha dado, todos los métodos devuelven el mismo objeto (esto), por lo que todos afectan al mismo objeto. Eso no es raro, pero es útil saber que no tiene que ser el caso; algunos o todos los métodos en la cadena pueden devolver diferentes objetos. Por ejemplo, es posible que también tienen métodos como:

Date Time::date() const; 
String Date::dayOfWeek() const; 

en cuyo caso se podría decir:

Time t; 
String day = t.date().dayOfWeek(); 

para obtener el nombre del día de la semana. En ese caso, t.date() devuelve un objeto Date que se utiliza a su vez para llamar al dayOfWeek().

+0

+1 solo para la primera frase, -1 para no decir 'Fecha y hora :: fecha() const;' y el uso de un vacío redundante. Por lo tanto, + -0. editar: Después de editar tu respuesta, la haré +1. –

+0

@phresnel, esa es una forma de hacerlo, pero diferente de lo que pretendía. ¿Por qué devolver un objeto nuevo en lugar de una referencia al existente? Devolver una referencia a un objeto existente parece igualmente válido, como se demuestra [aquí] (http://en.wikipedia.org/wiki/Method_chaining) y [aquí] (http://www.parashift.com/c++faq- lite/ctors.html # faq-10.20). También parece más eficiente y más natural cuando considera que el encadenamiento se usa a menudo para configurar un nuevo objeto. – Caleb

+0

De acuerdo, pensé que eran errores tipográficos. ¿Estás seguro de que 'dayOfWeek()' debería devolver una referencia a una cadena no const? ¿Por qué el 'void's redundante? Además, esos ejemplos devuelven '* this', y no instancias de otro tipo, que pueden introducir muy sutilmente errores peligrosos del tipo que posiblemente se muestra justo antes de su cliente. –

1

Podría ayudar si piensa que las declaraciones se resuelven paso por paso.

tomar las siguientes, por ejemplo:

x = 1 + 2 * 3 - 4; 
x = 1 + 6 - 4; 
x = 7 - 4; 
x = 3; 

C++ hace lo mismo con las llamadas a funciones y todo lo que haces dentro de una sentencia, la resolución de cada elemento en el interior en el orden de precedencia de los operadores. Por lo que puede pensar en su ejemplo, como ser resuelto de la misma manera:

t.setHour(18).setMinute(30).setSecond(22); 
t.setMinute(30).setSecond(22); // hour is now set to 18 
t.setSecond(22); // minute is now set to 30 
t; // seconds now set to 22 

Si usted regresó this en lugar de *this, y así devolver punteros en lugar de referencias, se podrían obtener el mismo efecto, salvo que sustituiría a la . con -> (solo como ejemplo, lo estás haciendo bien usando referencias). Del mismo modo, si devolvió un puntero o una referencia a un objeto diferente , podría hacer lo mismo con eso. Por ejemplo, supongamos que tiene una función que devuelve un objeto Time.

class Time{ 
    public: 
    int getSeconds(){ 
     return seconds; 
    }; 
    int seconds; 
}; 

Time getCurrentTime(){ 
    Time time = doSomethingThatGetsTheTime(); 
    return time; 
}; 

int seconds = getCurrentTime().getSeconds();  

Obtiene los segundos sin tener que dividir la instrucción en dos declaraciones diferentes o hacer una variable temporal para contener el objeto Time devuelto.

Esta pregunta C++: Using '.' operator on expressions and function calls va un poco más en profundidad si quieres leer.

1

porque cuando se ejecuta una función y regresa, devuelve la referencia a sí mismo, así que de nuevo puede llamar al functions.

Cuestiones relacionadas