AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • Início
  • system&network
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • Início
  • system&network
    • Recentes
    • Highest score
    • tags
  • Ubuntu
    • Recentes
    • Highest score
    • tags
  • Unix
    • Recentes
    • tags
  • DBA
    • Recentes
    • tags
  • Computer
    • Recentes
    • tags
  • Coding
    • Recentes
    • tags
Início / coding / Perguntas / 79039630
Accepted
Max
Max
Asked: 2024-09-30 21:42:05 +0800 CST2024-09-30 21:42:05 +0800 CST 2024-09-30 21:42:05 +0800 CST

Como alocar para posterior colocação novo "como se por novo"

  • 772

Precisamos separar alocação e inicialização de algum armazenamento de heap. Infelizmente, o código do cliente usa delete p;para excluir o ponteiro. Se tivéssemos controle da exclusão, poderíamos alocar com ::operator new (sizeof(T), std::align_val_t(alignof(T)))e então usar posicionamento newe o global correspondente ::operator delete. Mas (se entendi corretamente) deletepode chamar T::operator delete, potencialmente exigindo uma alocação que veio de T::operator new, com uma ordem de preferência complicada entre os diferentes argumentos possíveis para T::operator newe T::operator deletese houver múltiplas sobrecargas.

Existe algo assim std::allocate_as_if_by_new<T>()que fará exatamente a mesma alocação que new T(...)eu faria, mas sem inicializar, ou pelo menos garantirá que (após a inicialização) delete p;irá desalocá-la corretamente?

c++
  • 2 2 respostas
  • 825 Views

2 respostas

  • Voted
  1. Yakk - Adam Nevraumont
    2024-09-30T22:03:32+08:002024-09-30T22:03:32+08:00

    Não. O melhor que você pode fazer é usar algo como a expressão handle.

    O idioma handle é quando você passa um valor que não é um ponteiro real para seus dados reais. Você pode permitir que ele seja deletado fazendo com que o handle tenha um destrutor que você controla.

    struct Interface {
      virtual void DoStuff() const = 0;
      virtual ~Interface() {}
    };
    
    struct Forwarder:Interface {
      virtual void DoStuff() const { return pImpl->DoStuff(); }
      Interface* pImpl = 0;
      ~Forwarder() {
        /* delete pImpl using your internal mechanism */
      }
    };
    

    então retorne new Forwarder{ pRealObject }de suas APIs.

    Eles são deleteo Forwarderobjeto, que faz a limpeza adequada no seu objeto subjacente real pImpl. Se ele tiver uma tabela de função virtual, sua Forwarderimplementação apenas salta tudo para mexer com pImplisso.

    E se não houver uma tabela de funções virtuais (por exemplo, funções livres em um ADT), você ensina esses ADTs a converter os argumentos para Forwarder*então usá pImpl-los.

    Pode ajudar se você usar uma classe diferente para o público Interfacedo que sua interna pImplpara evitar chamar APIs públicas que esperam identificadores com o tipo interno e vice-versa; isso requer alguma duplicação de código ou alguma tolice de modelo.

    Em um ambiente mais parecido com C, o idioma do identificador se parece com:

    struct Handle;
    typedef Handle* HANDLE;
    HANDLE Create();
    /* internally */
    struct Handle { void* pImpl; };
    

    onde você retorna um ponteiro para uma estrutura opaca do seu código de criação.

    • 10
  2. Best Answer
    Turtlefight
    2024-10-01T03:17:11+08:002024-10-01T03:17:11+08:00

    Resposta curta: Não.


    Resposta longa:

    Quais ponteiros são operandos válidos para expressões de exclusão de objeto único (por exemplo, delete ptr;) são abordados no padrão por expr.delete:

    7.6.2.9 Expressões unárias - Excluir [expr.delete]

    (2) [...] Em uma expressão delete de objeto único, o valor do operando de delete pode ser um valor de ponteiro nulo, um ponteiro para um objeto não array criado por uma new-expression anterior , ou um ponteiro para um subobjeto representando uma classe base de tal objeto. Se não, o comportamento é undefined . [...]

    [ênfase minha]

    Então, se você quer delete ptr;ser definido, precisa ser:

    • um ponteiro nulo
    • um ponteiro retornado por uma nova expressão (por exemplo T* ptr = new T;)
      (ou no caso Tde ser do tipo classe, então um ponteiro para uma das Tclasses base de também é permitido)

    Observe que isso descarta imediatamente qualquer trapaça como chamar operator newmanualmente e depois usar placement-new para criar um objeto nessa memória.

    • O ponteiro usado como operando para a expressão de exclusão deve ser criado por uma nova expressão.
    • O tipo do ponteiro passado para a expressão de exclusão deve corresponder ao tipo do objeto alocado pela nova expressão (ou, para classes, também pode ser um ponteiro para a classe base).

    Existem algumas alternativas que você pode considerar:

    1. Use um alocador

    Os alocadores dividem a alocação e a construção em etapas separadas (e destruição e desalocação).

    Então, supondo que você possa alterar seu código para usar std::allocator(ou qualquer outro alocador que satisfaça os requisitos de completude do alocador ), seria possível alocar T's sem inicializá-los:

    template<class T>
    void example() {
        using alloc_traits = std::allocator_traits<std::allocator<T>>;
        std::allocator<T> alloc;
    
        T* ptr = alloc_traits::allocate(alloc, 1);
        // ptr is uninitialized
        
        // ...
    
        // construct a T at ptr
        alloc_traits::construct(alloc, ptr /* , ...constructor args */);
    
        // ...
    
        // "delete ptr;" replacement:
        alloc_traits::destroy(alloc, ptr);
        alloc_traits::deallocate(alloc, ptr, 1);
    }
    

    ou dado que você marcou sua pergunta com unique-ptr :

    template<class T>
    struct std_allocator_deleter {
        constexpr void operator()(T* ptr) const {
            using alloc_traits = std::allocator_traits<std::allocator<T>>;
            std::allocator<T> alloc;
    
            alloc_traits::destroy(alloc, ptr);
            alloc_traits::deallocate(alloc, ptr, 1);
        }
    };
    
    template<class T>
    std::unique_ptr<T, std_allocator_deleter<T>> allocate_unique() {
        using alloc_traits = std::allocator_traits<std::allocator<T>>;
        std::allocator<T> alloc;
    
        T* ptr = alloc_traits::allocate(alloc, 1);
        return std::unique_ptr<T, std_allocator_deleter<T>>(ptr);
    }
    
    
    template<class T>
    void example() {
        // allocate uninitialized storage
        auto ptr = allocate_unique<T>();
        // ...
        // construct a T 
        new(ptr.get()) T();
        // ...
        // ptr destructor automatically destroys & deallocates T
    }
    

    Observe que você deve garantir que sempre crie um novo Tobjeto nesse armazenamento, caso contrário, o std_allocator_deleterdeleter terá um comportamento indefinido.

    2. Não use uma expressão de exclusão

    Uma solução simples seria simplesmente não usar uma expressão de exclusão.
    Se você chamar explicitamente o destrutor seguido por uma chamada para operator deleteseu programa também seria bem comportado:

    T* ptr = (T*)::operator new(sizeof(T));
    // ...
    new(ptr) T();
    // ...
    
    
    
    // undefined behavior:
    // delete ptr;
    
    // OK:
    ptr->~T();
    ::operator delete(ptr);
    
    3. Crie um objeto com uma nova expressão e substitua-o

    Se seus Ttipos forem construtíveis por padrão de forma barata (ou você puder modificá-los para que sejam), você pode simplesmente usar uma new-expression normal e chamar seu destrutor imediatamente:

    template<class T>
    T* allocate_uninitialized() {
        T* ptr = new T;
        ptr->~T();
        return ptr;
    }
    
    template<class T>
    void example() {
        T* ptr = allocate_unitialized<T>();
        // ...
        new (ptr) T();
        // ...
        delete ptr;
    }
    

    Observe que você deve sempre colocar um novo Tobjeto no ponteiro retornado por allocate_uninitialized()antes de passá-lo para uma expressão de exclusão, caso contrário, o comportamento será indefinido.

    • 7

relate perguntas

  • Por que os compiladores perdem a vetorização aqui?

  • Erro de compilação usando CMake com biblioteca [fechada]

  • Erro lançado toda vez que tento executar o premake

  • Como criar um tipo de octeto semelhante a std::byte em C++?

  • Somente operações bit a bit para std::byte em C++ 17?

Sidebar

Stats

  • Perguntas 205573
  • respostas 270741
  • best respostas 135370
  • utilizador 68524
  • Highest score
  • respostas
  • Marko Smith

    Vue 3: Erro na criação "Identificador esperado, mas encontrado 'import'" [duplicado]

    • 1 respostas
  • Marko Smith

    Por que esse código Java simples e pequeno roda 30x mais rápido em todas as JVMs Graal, mas não em nenhuma JVM Oracle?

    • 1 respostas
  • Marko Smith

    Qual é o propósito de `enum class` com um tipo subjacente especificado, mas sem enumeradores?

    • 1 respostas
  • Marko Smith

    Como faço para corrigir um erro MODULE_NOT_FOUND para um módulo que não importei manualmente?

    • 6 respostas
  • Marko Smith

    `(expression, lvalue) = rvalue` é uma atribuição válida em C ou C++? Por que alguns compiladores aceitam/rejeitam isso?

    • 3 respostas
  • Marko Smith

    Quando devo usar um std::inplace_vector em vez de um std::vector?

    • 3 respostas
  • Marko Smith

    Um programa vazio que não faz nada em C++ precisa de um heap de 204 KB, mas não em C

    • 1 respostas
  • Marko Smith

    PowerBI atualmente quebrado com BigQuery: problema de driver Simba com atualização do Windows

    • 2 respostas
  • Marko Smith

    AdMob: MobileAds.initialize() - "java.lang.Integer não pode ser convertido em java.lang.String" para alguns dispositivos

    • 1 respostas
  • Marko Smith

    Estou tentando fazer o jogo pacman usando apenas o módulo Turtle Random e Math

    • 1 respostas
  • Martin Hope
    Aleksandr Dubinsky Por que a correspondência de padrões com o switch no InetAddress falha com 'não cobre todos os valores de entrada possíveis'? 2024-12-23 06:56:21 +0800 CST
  • Martin Hope
    Phillip Borge Por que esse código Java simples e pequeno roda 30x mais rápido em todas as JVMs Graal, mas não em nenhuma JVM Oracle? 2024-12-12 20:46:46 +0800 CST
  • Martin Hope
    Oodini Qual é o propósito de `enum class` com um tipo subjacente especificado, mas sem enumeradores? 2024-12-12 06:27:11 +0800 CST
  • Martin Hope
    sleeptightAnsiC `(expression, lvalue) = rvalue` é uma atribuição válida em C ou C++? Por que alguns compiladores aceitam/rejeitam isso? 2024-11-09 07:18:53 +0800 CST
  • Martin Hope
    The Mad Gamer Quando devo usar um std::inplace_vector em vez de um std::vector? 2024-10-29 23:01:00 +0800 CST
  • Martin Hope
    Chad Feller O ponto e vírgula agora é opcional em condicionais bash com [[ .. ]] na versão 5.2? 2024-10-21 05:50:33 +0800 CST
  • Martin Hope
    Wrench Por que um traço duplo (--) faz com que esta cláusula MariaDB seja avaliada como verdadeira? 2024-05-05 13:37:20 +0800 CST
  • Martin Hope
    Waket Zheng Por que `dict(id=1, **{'id': 2})` às vezes gera `KeyError: 'id'` em vez de um TypeError? 2024-05-04 14:19:19 +0800 CST
  • Martin Hope
    user924 AdMob: MobileAds.initialize() - "java.lang.Integer não pode ser convertido em java.lang.String" para alguns dispositivos 2024-03-20 03:12:31 +0800 CST
  • Martin Hope
    MarkB Por que o GCC gera código que executa condicionalmente uma implementação SIMD? 2024-02-17 06:17:14 +0800 CST

Hot tag

python javascript c++ c# java typescript sql reactjs html

Explore

  • Início
  • Perguntas
    • Recentes
    • Highest score
  • tag
  • help

Footer

AskOverflow.Dev

About Us

  • About Us
  • Contact Us

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve