Como lidar com alterações de design para reprovação de auto_ptr no C ++ 11?

12

Estamos testando uma biblioteca em C ++ 11 (ou seja, -std=c++11). A biblioteca usa auto_ptre esse padrão:

Foo* GetFoo()
{
    autoptr<Foo> ptr(new Foo);

    // Initialize Foo
    ptr->Initialize(...);

    // Now configure remaining attributes
    ptr->SomeSetting(...);

    return ptr.release();
}

O C ++ 11 foi descontinuado auto_ptr, então queremos nos afastar dele.

No entanto, o código suporta C ++ 03 e C ++ 11, portanto, não é tão simples quanto puxar auto_ptr. Também vale mencionar que a biblioteca não possui dependências externas. Ele usa C ++ 03; e não usa Autotools, Cmake, Boost, ...

Como devemos lidar com as alterações de design para se afastar do auto_ptrC ++ 11, mantendo a compatibilidade com o C ++ 03?

MetaFight
fonte
Algum dos auto_ptrescopos (ie std::auto_ptr), eles precisam ser ou o ponteiro inteligente pode ser obtido de algum outro espaço para nome?
Niall
Como um aparte, você pode querer dobrar Foo::Initializeem Foo::Foo.
MSalters
1
@MSalters - sim, essa sempre foi uma daquelas coisas que me senti um pouco desconfortável. A biblioteca foi projetada na década de 1990 e acho que o design era semelhante ao MFC. Ou seja, houve uma construção C ++ de nível inferior e, em seguida, uma construção de objeto de "nível superior". Eu acho que o recurso foi usado como uma troca para que as classes não tenham 6 ou 12 construtores diferentes. (Neste ponto, o que fiz foi analisado e garantimos que as variáveis ​​de membro dos tipos de POD sejam inicializadas com padrões sãos nos construtores C ++).

Respostas:

13

Em muitos aspectos, a std::unique_ptrsubstituição foi feita (mas é mais segura) std::auto_ptr, portanto, deve haver muito poucas (se houver) alterações de código necessárias além de (como você pede) direcionando o código para usar um unique_ptrou outro auto_ptr.

Existem algumas maneiras de fazer isso (e cada uma vem com suas próprias vantagens e desvantagens da lista) abaixo. Dado o exemplo de código fornecido, eu preferiria uma das duas primeiras opções .

Opção 1

#if __cplusplus >= 201103L
template <typename T>
using auto_ptr = std::unique_ptr<T>;
#else
using std::auto_ptr;
#endif

Tradeoffs;

  • Você introduz o auto_ptrnome no espaço para nome global; você pode atenuar isso definindo que é seu próprio espaço para nome "privado"
  • Depois de migrar para o C ++ 17 (acredito auto_ptrque será completamente removido), você poderá pesquisar e substituir mais facilmente

opção 2

template <typename T>
struct my_ptr {
    #if __cplusplus >= 201103L
    typedef std::unique_ptr<T> ptr;
    #else
    typedef std::auto_ptr<T> ptr;
    #endif
};

Tradeoffs;

  • Provavelmente mais complicado de se trabalhar, toda a auto_ptrnecessidade atual mudou de código para algo comomy_ptr<T>::ptr
  • Melhor segurança: os nomes não estão sendo introduzidos no espaço de nomes global

Opção 3

Um tanto controverso, mas se você estiver preparado para aguentar as advertências de ter uma stdclasse como base

#if __cplusplus >= 201103L
template <typename T>
using my_ptr = std::unique_ptr<T>;
#else
template <typename T>
class my_ptr : public std::auto_ptr<T> {
  // implement the constructors for easier use
  // in particular
  explicit my_ptr( X* p = 0 ) : std::auto_ptr(p) {}
};
#endif

Tradeoffs;

  • Não tente usar a classe herdada onde seria esperada uma base virtual (em particular, o destruidor não virtual). Não que isso deva ser um problema no caso - mas esteja ciente disso
  • Novamente, o código muda
  • Possíveis incompatibilidades de espaço para nome - tudo depende de como a classe do ponteiro é usada para começar

Opção 4

Coloque os ponteiros em uma nova classe e agregue as funções necessárias ao membro

template <typename T>
class my_ptr { // could even use auto_ptr name?
  #if __cplusplus >= 201103L
  std::unique_ptr<T> ptr_;
  #else
  std::auto_ptr<T> ptr_;
  #endif

  // implement functions required...
  T* release() { return ptr_.release(); }
};

Tradeoffs;

  • Um pouco extremo quando tudo o que você realmente deseja é "trocar" as implementações
Niall
fonte
Resposta muito boa. Na verdade, pesquisei um pouco e você realizou pelo menos três dos testes que tentei. (O que falta é o material específico do OS X e Clang. O OS X é um urso, porque ainda usa o espaço de nomes TR1 para C ++ 03 às vezes, e você deve incluir itens usando este método: Nenhum tipo chamado 'unique_ptr' no espaço de nome 'std' ao compilar sob LLVM / Clang ).
@jww. Estou no OS X (XCode 6.4 e Apple LLVM versão 6.1.0 (clang-602.0.53) (baseado no LLVM 3.6.0svn)) e não tenho problemas com o mix C ++ 03/11 que não seja o tr1namespace no por mais tempo (eu uso libc ++ e não libstdc ++). Eu sei que o tr1 não é normativo, mas não consigo encontrar em nenhum lugar do rascunho (aqui) que os arquivos precisavam estar <tr1/...>, o fato é que ele menciona estar apenas no cabeçalho <memory>etc. arquivo apenas no tr1namespace.
Niall
@jww. Eu acho que, dada uma combinação específica de compilador, biblioteca e dispositivo de destino - talvez você precise fazer mais algumas tentativas. Senão, no OS X, considere mudar para clang e libc ++. Sinceramente, considero a libc ++ a nova biblioteca C ++ "nativa" do OS X - por padrão. Não tenho como apoiar essas alegações, além de que o histórico do relacionamento clang / Apple e que as ferramentas GCC no OS X parecem desatualizadas (biblioteca) ou apenas removidas (tanto quanto sei, o GCC é um esboço simples para clangar de qualquer maneira )
Niall
"Caso contrário, no OS X, considere mudar para clang e libc ++ ..." - sim, eu meio que concordo com você. No entanto, gostaríamos de permitir que os usuários fizessem essa escolha e não forçá-la a eles. (Eles implicitamente fazem a escolha quando especificam (ou não) CXX=...).
Aqui está o caso que está me causando tantos problemas no Mac OS X 10.7 e 10.8: c++ -v -std=c++11 -x c++ - < /dev/null. I grep'da incluir diretórios que foram objecto de dumping, e eles não incluem unique_ptr.
0

Opção 5: Alias ​​direto.

#if __cplusplus >= 201103L
template<typename T> 
using MyPtr = std::unique_ptr<T>;
#else 
#define MyPtr std::auto_ptr
#endif 

Compensações:

  1. Para versões mais recentes do idioma, AKA C ++ 11 e posterior, seu tipo de alias é mapeado para o ponteiro inteligente correto. Qualquer código de usuário que realmente dependa de APIs específicas para std :: auto_ptr será sinalizado pelo compilador, que é a garantia máxima de que realmente será corrigido.

  2. No modo Legacy c ++ 03, o alias de tipo é uma macro. Isso é grosseiro, mas a sintaxe resultante MyPtr<T>será idêntica ao caso do C ++ 11 em todo o restante do código.

  3. Você precisa encontrar e alterar todas as suas variáveis ​​auto_ptr MyPtrpara configurar isso.

user3726672
fonte
1
Não está muito claro o que isso está referenciando (e, como foi dito, não é uma pergunta).
autophage
1
@ autophage Eu acredito que é uma resposta ... então provavelmente não é uma pergunta.
Kain0_0