una funcion ejemplo dev define declara constantes constante como c++ const getter member-functions

c++ - ejemplo - ¿Razones para definir funciones miembro ''no'' constantes?



constantes en c++ (6)

Depende del propósito de S Si se trata de una especie de envoltura delgada, podría ser apropiado permitir que el usuario acceda directamente al valor subyacente.

Uno de los ejemplos de la vida real es std::reference_wrapper .

Estoy trabajando en el aprendizaje de C ++ con el libro de Stroustrup (Programming Principles & Practice Using C ++). En un ejercicio definimos una estructura simple:

template<typename T> struct S { explicit S(T v):val{v} { }; T& get(); const T& get() const; void set(T v); void read_val(T& v); T& operator=(const T& t); // deep copy assignment private: T val; };

Luego se nos pide que definan una función miembro constante y no constante para obtener val .

Me preguntaba: ¿Hay algún caso en el que tenga sentido tener get función de get no constante que devuelva val ?

Me parece mucho más limpio que no podemos cambiar el valor en tales situaciones indirectamente. ¿Cuáles podrían ser los casos de uso en los que necesita una función de get constante y no constante para devolver una variable miembro?


No. Si un captador simplemente devuelve una referencia no constante a un miembro, como esto:

private: Object m_member; public: Object &getMember() { return m_member; }

Entonces m_member debería ser público en su lugar, y no se necesita el m_member . No tiene ningún sentido hacer que este miembro sea privado, y luego crear un elemento de acceso, lo que le da acceso a todo.

Si llama a getMember() , puede almacenar la referencia resultante a un puntero / referencia, y luego, puede hacer lo que quiera con m_member , la clase m_member no sabrá nada al respecto. Es lo mismo, como si un m_member hubiera sido público.

Tenga en cuenta que si getMember() realiza alguna tarea adicional (por ejemplo, no solo devuelve m_member , sino que lo construye perezosamente), getMember() podría ser útil:

Object &getMember() { if (!m_member) m_member = new Object; return *m_member; }


Primero déjame reformular tu pregunta:

¿Por qué tener un contratista que no sea una constante para un miembro, en lugar de hacerlo público?

Varias razones posibles razones:

1. Fácil de instrumentar

Quien dijo que el que no es constante necesita ser justo:

T& get() { return val; }

? Bien podría ser algo como:

T& get() { if (check_for_something_bad()) { throw std::runtime_error{"Attempt to mutate val when bad things have happened"); } return val; } However, as @BenVoigt suggests, it is more appropriate to wait until the caller actually tries to mutate the value through the reference before spewing an error.

2. Convención cultural / "el jefe lo dijo".

Algunas organizaciones hacen cumplir los estándares de codificación. Estos estándares de codificación a veces son creados por personas que posiblemente son demasiado defensivas. Entonces, puedes ver algo como:

A menos que su clase sea del tipo "datos antiguos" , ningún miembro de los datos puede ser público. Puede utilizar métodos de obtención para dichos miembros no públicos según sea necesario.

y luego, incluso si tiene sentido para una clase específica simplemente permitir el acceso no constante, no sucederá.

3. Tal vez val simplemente no está allí?

Has dado un ejemplo en el que realmente existe val en una instancia de la clase. Pero en realidad, ¡no tiene que ser así! El método get() podría devolver algún tipo de objeto proxy, que, tras la asignación, la mutación, etc., realiza algún cálculo (por ejemplo, almacenamiento o recuperación de datos en una base de datos).

4. Permite cambiar las clases internas más tarde sin cambiar el código de usuario

Ahora, leyendo los puntos 1 o 3, arriba, puedes preguntar "¡pero mi struct S tiene val !" o "por mi get() no hace nada interesante!" - bueno, cierto, no lo hacen; pero es posible que desee cambiar este comportamiento en el futuro. Sin un get() , todos los usuarios de su clase deberán cambiar su código. Con un get() , solo necesita realizar cambios en la implementación de struct S

Ahora, no abogo por este tipo de enfoque de enfoque de diseño, pero algunos programadores lo hacen.


También me gustaría señalar el lado práctico de la pregunta. Por ejemplo, desea agregar su Bar objetos a otro objeto Foo y crear el método constante doSomething que usa la Bar solo para leer:

class Foo { Bar bar_; public: void doSomething() const { //bar_.get(); } }

¡Tienes un problema! Estás absolutamente seguro de que no cambias el objeto Foo , quieres marcarlo con el modificador const , pero no puedes hacerlo porque get no es const . En otras palabras, crea sus propios problemas para el futuro y complica la escritura de un código claro y de alta calidad. Pero puedes implementar dos versiones para get si realmente lo necesitas:

class Bar { int data = 1; public: int& get() { return data; }; const int& get() const { return data; }; };

tal situación puede surgir si ya TIENE un código en el que se ignoran los modificadores constantes, por lo que tal situación es común:

foo(int* a); // Just get a, don`t modified foo(&bar_.get()); // need non constant get()


get() es invocable por objetos no const que pueden mutar, puedes hacer:

S r(0); r.get() = 1;

pero si hace r const as const S r(0) , la línea r.get() = 1 ya no se compila, ni siquiera para recuperar el valor, por eso necesita una versión const T& get() const const const T& get() const al menos Para poder recuperar el valor de los objetos const, esto le permite hacer:

const S r(0) int val = r.get()

La versión const de las funciones miembro intenta ser coherente con la propiedad constness del objeto en el que se realiza la llamada, es decir, si el objeto es inmutable al ser constante y la función miembro devuelve una referencia, puede reflejar la constancia del llamante devolviendo una referencia constante, preservando así la propiedad de inmutabilidad del objeto.


¿Quienes no son constantes?

Getters y setters son simplemente convención. En lugar de proporcionar un getter y un setter, un lenguaje usado a veces es proporcionar algo a lo largo de la línea de

struct foo { int val() const { return val_; } int& val() { return val_; } private: int val_; };

De modo que, dependiendo de la constancia de la instancia, obtendrá una referencia o una copia:

void bar(const foo& a, foo& b) { auto x = a.val(); // calls the const method returning an int b.val() = x; // calls the non-const method returning an int& };

Si este es un buen estilo en general es una cuestión de opinión. Hay casos en los que causa confusión y otros casos en los que este comportamiento es justo lo que cabría esperar (ver más abajo).

En cualquier caso, es más importante diseñar la interfaz de una clase de acuerdo con lo que se supone que debe hacer la clase y cómo desea usarla en lugar de seguir ciegamente las convenciones sobre los establecedores y los que obtienen (por ejemplo, debe darle al método un nombre significativo). eso expresa lo que hace, no solo en términos de "pretender estar encapsulado y ahora brindarme acceso a todos sus elementos internos a través de captadores", que es lo que realmente significa "usar captadores en todas partes").

Ejemplo concreto

Tenga en cuenta que el acceso a elementos en contenedores se implementa de esta manera Como ejemplo de juguete:

struct my_array { int operator[](unsigned i) const { return data[i]; } int& operator[](unsigned i) { return data[i]; } private: int data[10]; };

No es el trabajo de los contenedores ocultar los elementos al usuario (incluso los data pueden ser públicos). No desea que los diferentes métodos accedan a los elementos dependiendo de si desea leer o escribir el elemento, por lo tanto, proporcionar una sobrecarga const y no constante tiene mucho sentido en este caso.

Referencia no constante de la encapsulación get vs

Tal vez no sea tan obvio, pero es un poco controvertido si los captadores y definidores admiten la encapsulación o lo contrario. Si bien en general este asunto se basa en gran medida en la opinión, para los usuarios que devuelven referencias no constantes no se trata tanto de opiniones. Ellos rompen la encapusacion. Considerar

struct broken { void set(int x) { counter++; val = x; } int& get() { return x; } int get() const { return x; } private: int counter = 0; int value = 0; };

Esta clase está rota como sugiere su nombre. Los clientes simplemente pueden tomar una referencia y la clase no tiene oportunidad de contar el número de veces que se modifica el valor (como sugiere el set ). Una vez que devuelva una referencia no constante, entonces, con respecto a la encapsulación, hay poca diferencia en hacer público al miembro. Por lo tanto, esto se usa solo en los casos en que dicho comportamiento es natural (por ejemplo, un contenedor).

PD

Tenga en cuenta que su ejemplo devuelve una const T& lugar de un valor. Esto es razonable para el código de plantilla, donde no se sabe cuán cara es una copia, mientras que para un int no se gana mucho al devolver un const int& lugar de un int . En aras de la claridad, utilicé ejemplos sin plantillas, aunque para el código de plantilla probablemente preferiría devolver una const T& .