'cualquier' implementación de clase -- ++ campo con reinventing-the-wheel camp codereview Relacionados El problema

'any' class implementation


11
vote

problema

Español

HE HA HAZ UN 99887766555443377 CLASE EN C ++, BASE SONLELY EN boost::any , pero escrito de manera diferente. Estoy comprobando que lo he hecho correctamente y que no hay errores en él:

  class any { public:     any()         : dt(new data<void *>(0))         {         }      template<class T>         any(const T &value)         : dt(new data<T>(value))         {         }      any(any &rhs)         : dt(rhs.dt->duplicate())         {         }      ~any()         {         delete dt;         }      template<class T>         T cast() const         {         if (type() == typeid(T))             {             return (reinterpret_cast<data<T> *>(dt)->val);             }         throw std::exception("invalid cast type");         }      template<class T>         operator T() const         {         return (cast<T>());         }      template<class T>         bool is() const         {         return (type() == typeid(T));         }      any &operator=(any &rhs)         {         if (this != &rhs)             {             delete dt;             dt = rhs.dt->duplicate();             }         return (*this);         }      template<class T>         any &operator=(const T &value)         {         delete dt;         dt = new data<T>(value);         return (*this);         }      any &swap(any &rhs)         {         std::swap(dt, rhs.dt);         return (*this);         }      template<class T>         bool operator==(const T &value) const         {         return (type() == typeid(T) &&             cast<T>() == value);         }      bool operator==(any &rhs) const         {         return (type() == rhs.type() &&              dt->cmp(rhs.dt));         }      template<class T>         bool operator!=(const T &value) const         {         return (!((*this) == value));         }      bool operator!=(any &rhs) const         {         return (!((*this) == rhs));         }      const std::type_info &type() const         {         return (dt->type());         }  protected:     struct dummy         {     public:         virtual const std::type_info &type() const = 0;         virtual bool cmp(dummy *rhs) const = 0;         virtual dummy *duplicate() = 0;         };      template<class T>         struct data             : public dummy         {     public:         data()             : val()             {             }          data(const T &value)             : val(value)             {             }          ~data()             {             }          const std::type_info &type() const             {             return (typeid(T));             }          bool cmp(dummy *rhs) const             {             return (val == reinterpret_cast<data<T> *>(rhs)->val);             }          dummy *duplicate()             {             return (new data<T>(val));             }          T val;         };      dummy *dt;     };   
Original en ingles

I have made an any class in C++, base loosely on boost::any, but written differently. I am checking to see if I have done it correctly and that there are no mistakes in it:

class any { public:     any()         : dt(new data<void *>(0))         {         }      template<class T>         any(const T &value)         : dt(new data<T>(value))         {         }      any(any &rhs)         : dt(rhs.dt->duplicate())         {         }      ~any()         {         delete dt;         }      template<class T>         T cast() const         {         if (type() == typeid(T))             {             return (reinterpret_cast<data<T> *>(dt)->val);             }         throw std::exception("invalid cast type");         }      template<class T>         operator T() const         {         return (cast<T>());         }      template<class T>         bool is() const         {         return (type() == typeid(T));         }      any &operator=(any &rhs)         {         if (this != &rhs)             {             delete dt;             dt = rhs.dt->duplicate();             }         return (*this);         }      template<class T>         any &operator=(const T &value)         {         delete dt;         dt = new data<T>(value);         return (*this);         }      any &swap(any &rhs)         {         std::swap(dt, rhs.dt);         return (*this);         }      template<class T>         bool operator==(const T &value) const         {         return (type() == typeid(T) &&             cast<T>() == value);         }      bool operator==(any &rhs) const         {         return (type() == rhs.type() &&              dt->cmp(rhs.dt));         }      template<class T>         bool operator!=(const T &value) const         {         return (!((*this) == value));         }      bool operator!=(any &rhs) const         {         return (!((*this) == rhs));         }      const std::type_info &type() const         {         return (dt->type());         }  protected:     struct dummy         {     public:         virtual const std::type_info &type() const = 0;         virtual bool cmp(dummy *rhs) const = 0;         virtual dummy *duplicate() = 0;         };      template<class T>         struct data             : public dummy         {     public:         data()             : val()             {             }          data(const T &value)             : val(value)             {             }          ~data()             {             }          const std::type_info &type() const             {             return (typeid(T));             }          bool cmp(dummy *rhs) const             {             return (val == reinterpret_cast<data<T> *>(rhs)->val);             }          dummy *duplicate()             {             return (new data<T>(val));             }          T val;         };      dummy *dt;     }; 
     

Lista de respuestas

12
 
vote

Su código está en prácticamente buena forma, pero todavía hay varios problemas aparte de lo que mencionado por chrisw :

  • Hay muchos casos en los argumentos de entrada y los tipos de funciones de retorno donde no está especialmente cuidadoso const / const y valor vs. referencia < / em>.
  • Este código no funcionará para las matrices incorporadas de , por lo que ni para las cadenas de estilo C. Una forma es decadencia el tipo de argumento de entrada antes del almacenamiento; Las matrices incorporadas se deterioran a los punteros en este caso (lo que significa que los elementos de matriz no están realmente copiados).
  • El constructor predeterminado no debe asignar nada; Inicialice el puntero de datos a nullptr y proporcione las funciones miembro empty()5 y 9988776655544336 Para controlar el estado de tener / no tener datos.
  • Los operadores de asignación son innecesariamente complejos, ineficientes (por autopruebas) y no es seguro (si 9988777665544337 tira, el objeto actual ya está destruido). La solución más elegante para todos estos problemas es el Idiom de copia-swap , donde todo el trabajo real se realiza por constructores solo.
  • No necesita typeid para probar para tipo igualdad ; Una solución ligera (pero bajo nivel de nivel) sin RTTI es aquí .
  • Identificación de tipo debe mantenerse como un detalle interno; La funcionalidad mínima requerida es la igualdad de tipo mediante is() ; No exponga #region "Interface Implementation" public void Rollback() { } public void Validate() { // TODO: Write your own validation code } 0 , más bien, mantenlo lo más privado posible.
  • El chequeo de tipo es algo bueno, pero para el rendimiento también debe proporcionarle acceso sin marcar .
  • La fundición es de una base a una clase derivada, por lo que no es necesario hacerlo (y no debe) hacerse con #region "Interface Implementation" public void Rollback() { } public void Validate() { // TODO: Write your own validation code } 1 ; Más bien, #region "Interface Implementation" public void Rollback() { } public void Validate() { // TODO: Write your own validation code } 2 / #region "Interface Implementation" public void Rollback() { } public void Validate() { // TODO: Write your own validation code } 3 para el acceso revisado / sin marcar. #region "Interface Implementation" public void Rollback() { } public void Validate() { // TODO: Write your own validation code } 4 a un tipo de referencia se lanzará automáticamente un #region "Interface Implementation" public void Rollback() { } public void Validate() { // TODO: Write your own validation code } 5 si el objeto no es del tipo correcto, por lo que no es necesario verificar manualmente con #region "Interface Implementation" public void Rollback() { } public void Validate() { // TODO: Write your own validation code } 6 . Esto necesita RTTI, pero es más elegante.
  • almacenar objetos vacíos (como objetos de función) actualmente es ineficiente, ya que necesita espacio adicional en la parte superior de la tabla de funciones virtuales. Esto puede ser resuelto por el optimización de la base vacía , que se realiza automáticamente mediante el uso de #region "Interface Implementation" public void Rollback() { } public void Validate() { // TODO: Write your own validation code } 7 .
  • Los operadores de conversión implícitos son una posible fuente de ambigüedades y confusión; Puede mantenerlos si necesita su conveniencia, pero use cuidadosamente (por ejemplo, intente inicializar explícitamente un objeto del tipo correcto).
  • Los operadores de comparación son una excesiva clara (si las tienes, ¿por qué no tienen operadores aritméticos, y así sucesivamente?). Si aún lo desea, defina los Funciones no miembros , usando miembros públicos 99887766555443318 99887766555443319 para implementarlos.
  • no hay Mover semántica .
  • No hay una función binaria especializada (no miembro) const0 . Predeterminado para const1 no es tan eficiente, ya que involucra tres operaciones de movimiento; Sin mover la semántica, las cosas son aún peores, ya que involucra a las operaciones de tres copy .

Tomé la libertad de volver a factorizar su código en gran medida, y aquí está el resultado, resolviendo todos los problemas anteriores:

  const2  

ILO LLAMO const3 En lugar de const4 porque prefiero retener const5 En lugar de const6 cosa: -) PLUS, const7 es un nombre común para una función (como const8 ), que no es el caso de const9 .

MIEMBROS const0

Proporcionar acceso a los cheques de tipo, mientras que los miembros 998877665554433311 (Notación más corta) proporcionan acceso sin marcar. He optado por dar a los operadores de conversión sin marcar el acceso para el rendimiento, pero usted es libre de cambiar eso o eliminarlos por completo.

También he hecho una Ejemplo en vivo , incluida una extensa serie de pruebas para demostrar (casi) todos los usos posibles

Tenga en cuenta que const22 (su código> const33 ) ahora tiene un 99887776655443334 destructor.

Hay un problema más: el uso de la tienda gratuita es un cambio de botella de rendimiento grave cuando se trata de pequeños tipos. Lo que puede hacer es definir un tamaño particular que se asignará en pila , y solo use la tienda gratuita para objetos más grandes, similar a optimización de cadena corta (o pequeña) .

De manera más general, es una buena idea parametrizar la implementación con respecto a la gestión de la memoria (asignación, desasignación) al proporcionar un tipo de objeto . const5 es realmente un contenedor , incluso si es de más un objeto.

He extendido la implementación hacia esta dirección, pero probablemente publicaré esto como una respuesta separada.

 

Your code is in pretty much good shape but there are still several issues apart from what mentioned by ChrisW:

  • There are many cases in input arguments and return types of functions where you are not particularly careful about const/non-const and value vs. reference.
  • This code won't work for built-in arrays, hence neither for C-style strings. One way is to decay the type of the input argument before storage; built-in arrays are decayed to pointers in this case (which means array elements are not really copied).
  • The default constructor shouldn't allocate anything; initialize the data pointer to nullptr and provide member functions empty() and clear() to control the state of having/not having data.
  • Your assignment operators are unnecessarily complex, inefficient (by self-tests) and not exception-safe (if new throws, the current object is already destroyed). The most elegant solution to all these issues is the copy-swap idiom, where all actual work is done by constructors alone.
  • You don't need typeid to test for type equality; a lightweight (but low-level) solution without RTTI is here.
  • Type identification should be kept as an internal detail; the minimal required functionality is type equality by is(); don't expose type(), rather keep it as private as possible.
  • Type checking is a good thing, but for performance you should also provide unchecked access.
  • Casting is from a base to a derived class, so need not (and should not) be done with reinterpret_cast; rather, dynamic_cast / static_cast for checked / unchecked access. dynamic_cast to a reference type will automatically throw an std::bad_cast if the object is not of the right type, so there is not need to manually check with is(). This does need RTTI but is more elegant.
  • Storing empty objects (like function objects) is currently inefficient, as it does need extra space on top of the virtual function table. This can be solved by the empty base optimization, which is done automatically by using std::tuple.
  • Implicit conversion operators are a possible source for ambiguities and confusion; you may keep them if you need their convenience, but use carefully (e.g. try to explicitly initialize an object of the right type).
  • Comparison operators are a clear overkill (if you have them, why not also have arithmetic operators, and so on?). If you still want them, define them as non-member functions, using public members is and cast to implement them.
  • There are no move semantics.
  • There is no specialized binary (non-member) function swap. Defaulting to std::swap is not as efficient as it involves three move operations; without move semantics, things are even worse as it involves three copy operations.

I took the liberty to re-factor your code to a great extent, and here is the result, resolving all issues above:

class some {     using id = size_t;      template<typename T>     struct type { static void id() { } };      template<typename T>     static id type_id() { return reinterpret_cast<id>(&type<T>::id); }      template<typename T>     using decay = typename std::decay<T>::type;      template<typename T>     using none = typename std::enable_if<!std::is_same<some, T>::value>::type;      struct base     {         virtual ~base() { }         virtual bool is(id) const = 0;         virtual base *copy() const = 0;     } *p = nullptr;      template<typename T>     struct data : base, std::tuple<T>     {         using std::tuple<T>::tuple;          T       &get()      & { return std::get<0>(*this); }         T const &get() const& { return std::get<0>(*this); }          bool is(id i) const override { return i == type_id<T>(); }         base *copy()  const override { return new data{get()}; }     };      template<typename T>     T &stat() { return static_cast<data<T>&>(*p).get(); }      template<typename T>     T const &stat() const { return static_cast<data<T> const&>(*p).get(); }      template<typename T>     T &dyn() { return dynamic_cast<data<T>&>(*p).get(); }      template<typename T>     T const &dyn() const { return dynamic_cast<data<T> const&>(*p).get(); }  public:      some() { }     ~some() { delete p; }      some(some &&s)      : p{s.p} { s.p = nullptr; }     some(some const &s) : p{s.p->copy()} { }      template<typename T, typename U = decay<T>, typename = none<U>>     some(T &&x) : p{new data<U>{std::forward<T>(x)}} { }      some &operator=(some s) { swap(*this, s); return *this; }      friend void swap(some &s, some &r) { std::swap(s.p, r.p); }      void clear() { delete p; p = nullptr; }      bool empty() const { return p; }      template<typename T>     bool is() const { return p ? p->is(type_id<T>()) : false; }      template<typename T> T      &&_()     && { return std::move(stat<T>()); }     template<typename T> T       &_()      & { return stat<T>(); }     template<typename T> T const &_() const& { return stat<T>(); }      template<typename T> T      &&cast()     && { return std::move(dyn<T>()); }     template<typename T> T       &cast()      & { return dyn<T>(); }     template<typename T> T const &cast() const& { return dyn<T>(); }      template<typename T> operator T     &&()     && { return std::move(_<T>()); }     template<typename T> operator T      &()      & { return _<T>(); }     template<typename T> operator T const&() const& { return _<T>(); } }; 

I call it some instead of any because I prefer to hold something rather than anything :-) Plus, any is a common name for a function (like all), which is not the case for some.

Members cast() provide type-checked access, while members _() (shortest notation) provide unchecked access. I have chosen to give conversion operators unchecked access for performance but you are free to change that or remove them altogether.

I've also made a live example, including an extensive series of tests to demonstrate (almost) all possible uses.

Note that base (your dummy) now has a virtual destructor.

There is one more issue: using the free store is serious performance bottleneck when it comes to small types. What you can do is define a particular size to be allocated on stack, and only use the free store for larger objects, similar to short (or small) string optimization.

More generally, it is a good idea to parametrize the implementation with respect to how memory management (allocation, deallocation) is done by providing a kind of allocator object. any is really a container, even if of at most one object.

I have extended the implementation towards this direction but I will probably post this as a separate answer.

 
 
 
 
6
 
vote

Estoy comprobando para ver si lo he hecho correctamente y que no hay errores en él:

¿Qué has hecho para probar esta clase? Es posible que desee escribir algunas pruebas de unidad.


Creo que veo al menos un error: el destructor ...

  const6  

... llama const7 .

const8 parece ser de tipo const9 ...

  nullptr0  

... pero en realidad es de tipo nullptr1 que se deriva de nullptr2 .

nullptr3 se define sin un destructor virtual: Entonces, cuando llame nullptr4 luego el nullptr5 se llamará al destructor, pero el nullptr6 Destructor no se llamará, y, por lo tanto, el miembro de datos nullptr7655443347 nullptr8 destructor no se llamará, lo cual es un error si 99887766555443349 tiene un destructor no trivial (no predeterminado).


No veo por qué no define empty()0 como un método 99887776655443351 99887776655443352 constructor y el empty()3 Operador como TOMANDO empty()4 Parámetros de referencia.


Usted ha definido los detalles de la implementación de empty()5 AS empty()6 en lugar de empty()7 , como si esperara 99887776655443358 ser subclasificado Si empty()9 se subclujo entonces sus métodos (por ejemplo, su destructor) quizás deberán ser clear()0 .


Usted implementó clear()1 que es un operador de conversión; Pero Boost implementa una función explícita es decir, clear()2 Para este propósito. No estoy seguro de por qué Boost eligió lo último, pero puede ser mejor por alguna razón: tal vez sea más seguro porque es más explícito.

Boost Defina un tipo de excepción personalizado clear()3 Si el fundido falla; Solo está utilizando un clear()4 que podría ser más difícil de atrapar con precisión.

 

I am checking to see if I have done it correctly and that there are no mistakes in it:

What have you done to test this class? You might want to write some unit-tests.


I think I see at least one bug: the destructor ...

    ~any()     {     delete dt;     } 

... calls delete dt.

dt seems to be of type dummy* ...

dummy *dt; 

... but is actually of type data<T> which derives from dummy.

dummy is defined without a virtual destructor: so when you call delete dt then the dummy destructor will be called but the data destructor won't be called, and therefore the T data member of the data destructor won't be called, which is a bug if T has a non-trivial (non-default) destructor.


I don't see why you don't define duplicate as a const method, and define the any(any &rhs) constructor and the any &operator=(any &rhs) operator as taking const reference parameters.


You defined the implementation details of any as protected instead of private, as if you expect any to be subclassed. If any will be subclassed then its methods (e.g. its destructor) should perhaps be virtual.


You implemented operator T() const which is a conversion operator; but boost implements an explicit function i.e. any_cast for this purpose. I'm not sure why boost chose the latter but it may be better for some reason: perhaps it's safer because it's more explicit.

boost define a custom exception type bad_any_cast if the cast fails; you're just using a std::exception which might be harder to catch accurately.

 
 
     
     

Relacionados problema

6  C Getline () Implementación  ( C getline implementation ) 
Estoy practicando mi codificación C y quería implementar mi propia versión del getline Función en C para fines de aprendizaje. Me gustaría una revisión sobr...

11  Yaai (otro de otra implementación)  ( Yaai yet another any implementation ) 
Soy un desarrollador de juegos de C # actualmente aprendiendo C ++ y este es mi proyecto de segundo Big-ish (el primero es un Implementación vectorial ). E...

0  Producto cartesiano de dos tuplas - Python  ( Cartesian product of two tuples python ) 
Estoy resolviendo el ejercicio 4 de Discusión 3 de CS 61A (2012) de Berkley (2012) (consulte la página 4): Rellene la definición de cartesian_product . ...

4  Iniciar sesión en la base de datos con el núcleo ASP.NET  ( Logging into database with asp net core ) 
En el Container9 de mi aplicación ASP.NET Core Quiero configurar el 99887766655443330 para que inicie sesión en la base de datos. Por lo tanto, creo despu...

5  Método de cálculo del día del año  ( Day of year calculation method ) 
El ejercicio que quería resolver es de aquí . Copiando desde esa página: public static int dayOfYear(int month, int dayOfMonth, int year) { if (month...

3  Implementando de forma libre, crecer y encogerse en una tabla de hash  ( Implementing cleartable grow and shrink on a hash table ) 
He pasado por preguntas anteriores sobre la implementación de tablas de hash en Python, pero tengo algo más específico. Estoy manejando colisiones con sondeo ...

12  Autenticación simple en ASP.NET MVC 5  ( Simple authentication in asp net mvc 5 ) 
Estoy construyendo una aplicación ASP.NET MVC 5 y, por razones que son irrelevantes en este punto, estoy intentando construir mi propio medio para autenticar ...

2  Fusionar la implementación de Sort Java  ( Merge sort java implementation ) 
¿Puede alguien revisar mi implementación de tipo de fusión en Java? ¿Es bueno / malo y puede mejorarse más? public class MyMergeSort { private int [] d...

9  Comandos de Linux en Python  ( Linux commands in python ) 
He decidido escribir algunos comandos de Linux en Python. A continuación se muestra una lista, junto con algunas restricciones (si no está familiarizado con L...

5  Implementar el análisis de STRTOD  ( Implement strtod parsing ) 
en este comentario la OP escribió, Soy un novato, así que me gustaría saber ¿Cómo me gustaría analizar los números / argumentos de Negetive? en Esta ...




© 2022 respuesta.top Reservados todos los derechos. Centro de preguntas y respuestas reservados todos los derechos