Pergunta

Esta é uma questão de iniciantes, mas eu não fiz C ++ há muito tempo, então aqui vai ...

Eu tenho uma classe que contém uma matriz alocada dinamicamente, digamos

class A
{
    int* myArray;
    A()
    {
        myArray = 0;
    }
    A(int size)
    {
        myArray = new int[size];
    }
    ~A()
    {
        // Note that as per MikeB's helpful style critique, no need to check against 0.
        delete [] myArray;
    }
}

Mas agora quero criar uma matriz alocada dinamicamente dessas classes. Aqui está o meu código atual:

A* arrayOfAs = new A[5];
for (int i = 0; i < 5; ++i)
{
    arrayOfAs[i] = A(3);
}

Mas isso explode terrivelmente. Porque o novo A objeto criado (com o A(3) ligue) é destruído quando o for A iteração do loop termina, e isso significa que o interno myArray por essa A instância recebe delete []-Ed.

Então eu acho que minha sintaxe deve estar terrivelmente errada? Eu acho que existem algumas correções que parecem exageradas, que espero evitar:

  • Criando um construtor de cópia para A.
  • Usando vector<int> e vector<A> Portanto, não preciso me preocupar com tudo isso.
  • Em vez de ter arrayOfAs ser uma variedade de A objetos, são uma variedade de A* Ponteiros.

Eu acho que isso é apenas uma coisa para iniciantes em que há uma sintaxe que realmente funciona ao tentar alocar dinamicamente uma variedade de coisas que têm alocação dinâmica interna.

(Além disso, as críticas ao estilo apreciaram, já que faz um tempo desde que eu fiz C ++.)

Atualização para futuros espectadores: Todas as respostas abaixo são realmente úteis. O Martin's é aceito por causa do código de exemplo e da "regra dos 4" úteis, mas eu realmente sugiro ler todos eles. Alguns são declarações boas e sucintas do que está errado, e outras apontam corretamente como e por quê vectorS é um bom caminho a percorrer.

Foi útil?

Solução

Para a construção de contêineres, você obviamente deseja usar um dos contêineres padrão (como um vetor STD ::). Mas este é um exemplo perfeito das coisas que você precisa considerar quando seu objeto contém ponteiros crus.

Se o seu objeto tiver um ponteiro bruto, você precisará se lembrar da regra 3 (agora a regra de 5 em C ++ 11).

  • Construtor
  • Destruidor
  • Copiar construtor
  • Operador de atribuição
  • Mover construtor (C ++ 11)
  • Mover atribuição (C ++ 11)

Isso ocorre porque, se não for definido, o compilador gerará sua própria versão desses métodos (veja abaixo). As versões geradas pelo compilador nem sempre são úteis ao lidar com ponteiros crus.

O construtor de cópias é o difícil de correr (não é trivial se você deseja fornecer a forte garantia de exceção). O operador de atribuição pode ser definido em termos do construtor de cópias, pois você pode usar a cópia e trocar idiom internamente.

Veja abaixo os detalhes completos sobre o mínimo absoluto para uma classe que contém um ponteiro para uma variedade de números inteiros.

Sabendo que não é trivial corrigi -lo, considere usar o vetor STD :: em vez de um ponteiro para uma variedade de números inteiros. O vetor é fácil de usar (e expandir) e abrange todos os problemas associados a exceções. Compare a aula a seguir com a definição de um abaixo.

class A
{ 
    std::vector<int>   mArray;
    public:
        A(){}
        A(size_t s) :mArray(s)  {}
};

Olhando para o seu problema:

A* arrayOfAs = new A[5];
for (int i = 0; i < 5; ++i)
{
    // As you surmised the problem is on this line.
    arrayOfAs[i] = A(3);

    // What is happening:
    // 1) A(3) Build your A object (fine)
    // 2) A::operator=(A const&) is called to assign the value
    //    onto the result of the array access. Because you did
    //    not define this operator the compiler generated one is
    //    used.
}

O operador de atribuição gerado pelo compilador é bom para quase todas as situações, mas quando os ponteiros crus estão em jogo, você precisa prestar atenção. No seu caso, está causando um problema por causa do cópia rasa problema. Você acabou com dois objetos que contêm ponteiros para a mesma peça de memória. Quando o A (3) sai do escopo no final do loop, ele chama de excluir [] em seu ponteiro. Assim, o outro objeto (na matriz) agora contém um ponteiro para a memória que foi devolvido ao sistema.

O construtor de cópias gerado pelo compilador; Copia cada variável de membro usando os membros copiar o construtor. Para os ponteiros, isso significa apenas que o valor do ponteiro é copiado do objeto de origem para o objeto de destino (daí a cópia superficial).

O operador de atribuição gerado pelo compilador; Copia cada variável de membro usando o operador de atribuição desse membro. Para os ponteiros, isso significa apenas que o valor do ponteiro é copiado do objeto de origem para o objeto de destino (daí a cópia superficial).

Portanto, o mínimo para uma aula que contém um ponteiro:

class A
{
    size_t     mSize;
    int*       mArray;
    public:
         // Simple constructor/destructor are obvious.
         A(size_t s = 0) {mSize=s;mArray = new int[mSize];}
        ~A()             {delete [] mArray;}

         // Copy constructor needs more work
         A(A const& copy)
         {
             mSize  = copy.mSize;
             mArray = new int[copy.mSize];

             // Don't need to worry about copying integers.
             // But if the object has a copy constructor then
             // it would also need to worry about throws from the copy constructor.
             std::copy(&copy.mArray[0],&copy.mArray[c.mSize],mArray);

         }

         // Define assignment operator in terms of the copy constructor
         // Modified: There is a slight twist to the copy swap idiom, that you can
         //           Remove the manual copy made by passing the rhs by value thus
         //           providing an implicit copy generated by the compiler.
         A& operator=(A rhs) // Pass by value (thus generating a copy)
         {
             rhs.swap(*this); // Now swap data with the copy.
                              // The rhs parameter will delete the array when it
                              // goes out of scope at the end of the function
             return *this;
         }
         void swap(A& s) noexcept
         {
             using std::swap;
             swap(this.mArray,s.mArray);
             swap(this.mSize ,s.mSize);
         }

         // C++11
         A(A&& src) noexcept
             : mSize(0)
             , mArray(NULL)
         {
             src.swap(*this);
         }
         A& operator=(A&& src) noexcept
         {
             src.swap(*this);     // You are moving the state of the src object
                                  // into this one. The state of the src object
                                  // after the move must be valid but indeterminate.
                                  //
                                  // The easiest way to do this is to swap the states
                                  // of the two objects.
                                  //
                                  // Note: Doing any operation on src after a move 
                                  // is risky (apart from destroy) until you put it 
                                  // into a specific state. Your object should have
                                  // appropriate methods for this.
                                  // 
                                  // Example: Assignment (operator = should work).
                                  //          std::vector() has clear() which sets
                                  //          a specific state without needing to
                                  //          know the current state.
             return *this;
         }   
 }

Outras dicas

Eu recomendo usar std :: vetor: algo como

typedef std::vector<int> A;
typedef std::vector<A> AS;

Não há nada de errado com o leve exagero do STL, e você poderá gastar mais tempo implementando os recursos específicos do seu aplicativo em vez de reinventar a bicicleta.

O construtor do seu objeto A aloca outro objeto dinamicamente e armazena um ponteiro para esse objeto alocado dinamicamente em um ponteiro bruto.

Para esse cenário, você devo Defina seu próprio construtor de cópias, operador de atribuição e destruidor. O compilador gerado não funcionará corretamente. (Este é um corolário da "Lei dos Três Grandes": uma classe com qualquer destruidor, operador de atribuição, construtor de cópias geralmente precisa de todos os 3).

Você definiu seu próprio destruidor (e mencionou a criação de um construtor de cópias), mas precisa definir os outros dois dos três grandes.

Uma alternativa é armazenar o ponteiro para o seu alocado dinamicamente int[] Em algum outro objeto que cuidará dessas coisas para você. Algo como um vector<int> (como você mencionou) ou um boost::shared_array<>.

Para resumir isso - para aproveitar o RAII em toda a extensão, você deve evitar lidar com ponteiros crus na medida do possível.

E como você pediu outras críticas ao estilo, uma menor é que, quando você está excluindo indicadores crus, não precisa verificar 0 antes de ligar delete - delete Lida com esse caso fazendo nada para que você não precise confundir o código com as verificações.

  1. Use a matriz ou contêiner comum apenas para objetos se eles tiverem padrão e copiar construtores.

  2. Armazene ponteiros de outra forma (ou ponteiros inteligentes, mas podem atender a alguns problemas neste caso).

PS: sempre defina os próprios construtores de inadimplência e copiar, caso contrário gerados automaticamente, serão usados

Você precisa de um operador de atribuição para que:

arrayOfAs[i] = A(3);

funciona como deveria.

Por que não ter um método SetSize.

A* arrayOfAs = new A[5];
for (int i = 0; i < 5; ++i)
{
    arrayOfAs[i].SetSize(3);
}

Gosto da "cópia", mas neste caso o construtor padrão não está realmente fazendo nada. O SetSize poderia copiar os dados do M_Array original (se existir). Você precisaria armazenar o tamanho da matriz dentro da classe para fazer isso.
OU
O SetSize pode excluir o M_Array original.

void SetSize(unsigned int p_newSize)
{
    //I don't care if it's null because delete is smart enough to deal with that.
    delete myArray;
    myArray = new int[p_newSize];
    ASSERT(myArray);
}

Usando o recurso de colocação de new Operador, você pode criar o objeto no lugar e evitar copiar:

posicionamento (3): void* operador novo (std :: size_t tamanho, void* ptr) noexcept;

Simplesmente retorna PTR (nenhum armazenamento é alocado). Observe que, se a função for chamada por uma nova expressão, a inicialização adequada será executada (para objetos de classe, isso inclui chamar seu construtor padrão).

Eu sugiro o seguinte:

A* arrayOfAs = new A[5]; //Allocate a block of memory for 5 objects
for (int i = 0; i < 5; ++i)
{
    //Do not allocate memory,
    //initialize an object in memory address provided by the pointer
    new (&arrayOfAs[i]) A(3);
}
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top