Buen patrón de diseño para un contenedor c ++ alrededor de un objeto c

7

He escrito un contenedor extensible de c ++ alrededor de una biblioteca c muy difícil de usar pero también muy útil. El objetivo es tener la conveniencia de c ++ para asignar el objeto, exponer sus propiedades, desasignar el objeto, copiar semántica, etc. ...

El problema es este: a veces la biblioteca c quiere el objeto subyacente (un puntero al objeto), y el destructor de clases no debe destruir la memoria subyacente. Mientras que la mayoría de las veces, el destructor debe desasignar el objeto subyacente. He experimentado con la configuración de un indicador bool hasOwnership en la clase para que el destructor, el operador de asignación, etc ... sepa si debe liberar la memoria subyacente o no. Sin embargo, esto es incómodo para el usuario y, a veces, no hay forma de saber cuándo otro proceso utilizará esa memoria.

Actualmente, lo tengo configurado donde, cuando la asignación proviene de un puntero del mismo tipo que el tipo subyacente, a continuación, configuro el indicador hasOwnership. Hago lo mismo cuando se llama al constructor sobrecargado utilizando el puntero de la biblioteca c. Sin embargo, esto aún no controla el caso cuando el usuario ha creado el objeto y lo ha pasado a una de mis funciones que llama a c_api y la biblioteca almacena el puntero para su uso posterior. Si tuvieran que eliminar su objeto, entonces sin duda causaría un segfault en la biblioteca c.

¿Hay un patrón de diseño que simplifique este proceso? Tal vez algún tipo de recuento de referencias?

    
pregunta Jonathan Henson 05.01.2013 - 00:24

4 respuestas

4

Si la responsabilidad de limpiar las cosas asignadas dinámicamente se desplaza entre su clase de envoltura y la biblioteca de C en función de cómo se utilizan las cosas, está tratando con una biblioteca de C mal diseñada o está tratando de hacer demasiado en su envoltura clase.

En el primer caso, todo lo que puede hacer es hacer un seguimiento de quién es responsable de la limpieza y esperar que no se cometan errores (ni usted ni los encargados de la biblioteca de C).

En el segundo caso, debes repensar el diseño de tu envoltorio. ¿Todas las funciones pertenecen juntas en la misma clase, o pueden dividirse en múltiples clases? Quizás la biblioteca C use algo similar al Patrón de diseño de fachada y debería mantener una estructura similar en su envoltorio de C ++.

En cualquier caso, incluso si la biblioteca C es responsable de limpiar algunas cosas, no hay nada de malo en mantener una referencia / puntero a esas cosas. Solo debe recordar que no es responsable de limpiar a qué se refiere el puntero.

    
respondido por el Bart van Ingen Schenau 05.01.2013 - 13:06
3

A menudo puedes usar un patrón como este:

class C {
public:
  void foo() {
    underlying_foo(handle.get());
  }

  void bar() {
    // transfers ownership
    underlying_bar(handle.release());
  }

  // use default copy/move constructor and assignment operator

private:
  struct deleter {
    void operator()(T* ptr) {
      deleter_fn(ptr);
    }
  };
  std::unique_ptr<T, deleter> handle;
};

Al utilizar release , puede transferir explícitamente la propiedad. Sin embargo, esto es confuso y debe evitarlo si es posible.

La mayoría de las bibliotecas de C tienen un ciclo de vida de objeto similar a C ++ (asignación de objetos, accesores, destrucción) que se mapea muy bien en el patrón C ++ sin transferencia de propiedad.

Si los usuarios necesitan propiedad compartida, deben usar shared_ptr con sus clases. No intentes implementar ninguna propiedad compartiendo tú mismo.

Actualización: si desea que la transferencia de propiedad sea más explícita, puede usar un calificador de referencia:

void bar() && { ... }

Luego, los usuarios deben llamar a bar en valores como l:

C o;
std::move(o).bar();  // transfer of ownership is explicit at call site
    
respondido por el Philipp 05.01.2013 - 04:42
0

Hay una respuesta directa a su problema, punteros inteligentes. Al utilizar un puntero inteligente para contener la memoria de la biblioteca C y agregar una referencia cuando el puntero también se encuentra en la biblioteca (y eliminar la referencia cuando regresa la biblioteca C), liberará automáticamente la memoria cuando el número de referencia caiga a cero (y solo entonces)

    
respondido por el Michael Shaw 05.01.2013 - 01:55
0

Si la biblioteca puede liberar cosas internamente y los escenarios en los que esto puede suceder están bien documentados, entonces todo lo que puedes hacer es establecer una marca como la que ya estás haciendo.

    
respondido por el James 05.01.2013 - 05:01

Lea otras preguntas en las etiquetas