Por que o C ++ não permite que você pegue o endereço de um construtor?


14

Existe uma razão específica para isso quebrar a linguagem conceitualmente ou uma razão específica para que isso seja tecnicamente inviável em alguns casos?

O uso seria com o novo operador.

Edit: Vou desistir da esperança de obter o meu "novo operador" e "operador novo" direto e ser direto.

O ponto da questão é: por que os construtores são especiais ? Lembre-se, é claro, que as especificações de linguagem nos dizem o que é legal, mas não necessariamente moral. O que é legal é normalmente informado pelo que é logicamente consistente com o restante do idioma, o que é simples e conciso e o que é viável para os compiladores implementarem. A possível justificativa do comitê de padrões na ponderação desses fatores é deliberada e interessante - daí a questão.


Não seria um problema tomar o endereço de um construtor, mas poder transmitir o tipo. Modelos podem fazer isso.
Euphoric

E se você tiver um modelo de função que deseja construir um objeto usando um construtor que será especificado como argumento para a função?
Praxeolitic

1
Haverá alternativas para qualquer exemplo que eu possa pensar, mas ainda assim, por que os construtores devem ser especiais? Há muitas coisas que você provavelmente não usará na maioria das linguagens de programação, mas casos especiais como esse geralmente vêm com uma justificativa.
Praxeolitic

1
@RobertHarvey A pergunta me ocorreu quando eu estava prestes a digitar uma classe de fábrica.
Praxeolitic

1
Gostaria de saber se o C ++ 11 std::make_uniquee std::make_sharedpode resolver adequadamente a motivação prática subjacente a esta pergunta. Esses são métodos de modelo, o que significa que é necessário capturar os argumentos de entrada para o construtor e encaminhá-los para o construtor real.
Rwong 9/09/16

Respostas:


10

As funções ponteiros para membro fazem sentido apenas se você tiver mais de uma função membro com a mesma assinatura - caso contrário, haveria apenas um valor possível para o ponteiro. Mas isso não é possível para os construtores, pois em C ++ diferentes construtores da mesma classe devem ter assinaturas diferentes.

A alternativa para Stroustrup teria sido escolher uma sintaxe para C ++, na qual os construtores pudessem ter um nome diferente do nome da classe - mas isso impediria alguns aspectos muito elegantes da sintaxe do ctor existente e tornaria a linguagem mais complicada. Para mim, isso parece um preço alto apenas para permitir um recurso raramente necessário, que pode ser facilmente simulado pela "terceirização" da inicialização de um objeto do ctor para uma initfunção diferente (uma função de membro normal para a qual o ponteiro para membros pode ser criada).


2
Ainda assim, por que impedir memcpy(buffer, (&std::string)(int, char), size)? (Provavelmente extremamente un-kosher, mas este é C ++ depois de tudo.)
Thomas Eding

3
desculpe, mas o que você escreveu não faz sentido. Não vejo nada de errado em ter ponteiro para membro apontando para um construtor. Além disso, parece que você citou alguma coisa, sem link para a fonte.
BЈовић

1
@ Thomashoding: o que exatamente você espera que essa declaração faça? Copiando o código de montagem do string ctor de alguma forma? Como o "tamanho" será determinado (mesmo se você tentar algo equivalente a uma função de membro padrão)?
Doc Brown

Eu esperaria que ele fizesse o mesmo que faria com o endereço de um ponteiro de função livre memcpy(buffer, strlen, size). Presumivelmente, ele copiaria a montagem, mas quem sabe. Se o código pode ou não ser chamado sem travar, é necessário conhecimento sobre o compilador usado. O mesmo vale para determinar o tamanho. Seria altamente dependente da plataforma, mas muitas construções C ++ não portáteis são usadas no código de produção. Não vejo razão para proibir isso.
Thomas Eding

@ThomasEding: Espera-se que um compilador C ++ em conformidade forneça um diagnóstico ao tentar acessar um ponteiro de função como se fosse um ponteiro de dados. Um compilador C ++ não conforme poderia fazer qualquer coisa, mas também pode fornecer uma maneira não c ++ de acessar um construtor também. Esse não é um motivo para adicionar um recurso ao C ++ que não tenha utilidade no código em conformidade.
Bart van Ingen Schenau

5

Um construtor é uma função que você chama quando o objeto ainda não existe, portanto, não pode ser uma função de membro. Pode ser estático.

Na verdade, um construtor é chamado com um ponteiro this, depois que a memória foi alocada, mas antes de ser completamente inicializada. Como conseqüência, um construtor possui vários recursos privilegiados.

Se você tivesse um ponteiro para um construtor, ele teria que ser um ponteiro estático, algo como uma função de fábrica ou um ponteiro especial para algo que seria chamado imediatamente após a alocação de memória. Não poderia ser uma função de membro comum e ainda funcionaria como construtor.

O único objetivo útil que vem à mente é um tipo especial de ponteiro que pode ser passado ao novo operador para permitir que ele indique qual construtor usar. Acho que isso poderia ser útil, mas exigiria uma nova sintaxe significativa e, presumivelmente, a resposta é: eles pensaram sobre isso e não valeu a pena o esforço.

Se você apenas deseja refatorar o código de inicialização comum, uma função de memória comum é geralmente uma resposta suficiente e você pode obter um ponteiro para um deles.


Esta parece ser a resposta mais correta. Lembro-me de um artigo de muitos (muitos) anos atrás sobre o novo operador e o funcionamento interno do "novo operador". operador new () aloca espaço. O novo operador chama o construtor com esse espaço alocado. Tomar o endereço de um construtor é "especial", porque chamar o construtor requer espaço. O acesso para chamar um construtor como este é com o canal new.
Bill Porta

1
A palavra "existir" oculta os detalhes de que um objeto pode ter um endereço e ter memória alocada, mas não ser inicializado. Na função membro ou não, acho que obter o ponteiro this torna uma função uma função membro porque está claramente associada a uma instância de objeto (mesmo que não inicializada). Dito isto, a resposta levanta um bom ponto: o construtor é a única função de membro que pode ser chamada em um objeto não inicializado.
precisa saber é o seguinte

Deixa pra lá, aparentemente eles têm a designação de "funções-membro especiais". Cláusula 12 do padrão C ++ 11: "O construtor padrão (12.1), o construtor de cópias e o operador de atribuição de cópias (12.8), o construtor de movimentação e o operador de atribuição de movimentação (12.8) e o destrutor (12.4) são funções-membro especiais ."
precisa saber é o seguinte

E 12.1: "Um construtor não deve ser virtual (10.3) ou estático (9.4)." (grifo meu)
Praxeolitic

1
O fato é que, se você compilar com símbolos de depuração e procurar um rastreamento de pilha, haverá realmente um ponteiro para o construtor. O que eu nunca fui capaz é encontrar a sintaxe para obter esse ponteiro ( &A::Anão funciona em qualquer um dos compiladores que eu tentei.)
ALFC

-3

Isso ocorre porque o construtor não é do tipo retorno e você não está reservando espaço para o construtor na memória. Como você faz em caso de variável durante a declaração. Por exemplo: se você escrever uma variável simples X, o compilador gerará um erro porque o compilador não entenderá o significado disso. Mas quando você escreve Int x; Em seguida, o compilador passou a saber que ele digita a variável de dados, portanto, reservará algum espaço para a variável.

Conclusão: - portanto, a conclusão é que, devido à exclusão do tipo de retorno, ele não receberá o endereço na memória.


1
O código no construtor precisa ter um endereço na memória porque precisa estar em algum lugar. Não há necessidade de reservar espaço para ele na pilha, mas deve estar em algum lugar na memória. Você pode pegar o endereço de funções que não retornam valores. (void)(*fptr)()declara um ponteiro para uma função sem valor de retorno.
Praxeolitic 10/10

2
Você perdeu o objetivo da pergunta - a postagem original perguntou sobre o endereço do código para o construtor, não o resultado que o construtor forneceu. Além disso, neste quadro, use palavras completas: "u" não é um substituto aceitável para "você".
BobDalgleish

Sr. praxeolitic, acho que se não mencionarmos nenhum tipo de retorno, o compilador não definirá um local de memória específico para o ctor e o local será definido internamente ... Podemos buscar o endereço de qualquer coisa em c ++ que não seja fornecida por compilador? Se estou errado, então por favor me corrigir com a resposta correta
lovish Goyal

E também me fale sobre variável de referência. Podemos buscar o endereço da variável de referência? Se não, qual endereço printf ("% u", & (& (j))); está imprimindo se & j = x onde x = 10? Porque endereço impressos por printf e endereço de X não são os mesmos
lovish Goyal

-4

Vou adivinhar:

O construtor e destruidor de C ++ não são funções: são macros. Eles são incorporados no escopo onde o objeto é criado e no escopo em que o objeto é destruído. Por sua vez, não há construtor nem destruidor, o objeto é apenas IS.

Na verdade, acho que as outras funções da classe também não são funções, mas funções inline que NÃO são incorporadas porque você usa o endereço delas (o compilador percebe que você está interessado nela e não alinha ou alinha o código na função e otimiza essa função) e, por sua vez, a função parece "ainda estar lá", mesmo que não estivesse se você ainda não o tivesse abordado.

A tabela virtual do "objeto" do C ++ não é como um objeto JavaScript, onde você pode obter seu construtor e criar objetos a partir dele em tempo de execução new XMLHttpRequest.constructor, mas uma coleção de ponteiros para funções anônimas que atuam como meio de interface com esse objeto , excluindo a capacidade de criar o objeto. E nem faz sentido "excluir" o objeto, porque é como tentar excluir uma estrutura, você não pode: é apenas um rótulo de pilha, basta escrever nele como quiser sob outro rótulo: você é livre para use uma classe como 4 números inteiros:

/* i imagine this string gets compiled into a struct, one of which's members happens to be a const char * which is initialized to exactly your string: no function calls are made during construction. */
std::string a = "hello, world";
int *myInt = (int *)(*((void **)&a));
myInt[0] = 3;
myInt[1] = 9;
myInt[2] = 20;
myInt[3] = 300;

Não há vazamento de memória, não há problemas, exceto que você efetivamente desperdiçou um monte de espaço de pilha reservado para a interface do objeto e a cadeia de caracteres, mas isso não destruirá seu programa (desde que você não tente usá-lo) como uma corda novamente).

Na verdade, se minhas suposições anteriores estiverem corretas: o custo completo da string é apenas o custo de armazenar esses 32 bytes e o espaço constante da string: as funções são usadas apenas em tempo de compilação e também podem ser incorporadas e descartadas depois o objeto é criado e usado (como se você estivesse trabalhando com uma estrutura e se referisse a ela diretamente sem nenhuma chamada de função, verifique se há chamadas duplicadas em vez de saltos de função, mas isso geralmente é mais rápido e ocupa menos espaço). Em essência, sempre que você chama alguma função, o compilador substitui essa chamada pelas instruções para literalmente fazê-lo, com exceções que os designers de linguagem definiram.

Resumo: objetos C ++ não têm idéia do que são; todas as ferramentas para interagir com eles são alinhadas estaticamente e perdidas no tempo de execução. Isso torna o trabalho com classes tão eficiente quanto o preenchimento de estruturas com dados e o trabalho direto com esses dados sem chamar nenhuma função (essas funções são incorporadas).

Isso é completamente diferente das abordagens do COM / ObjectiveC e do javascript, que retêm as informações de tipo dinamicamente, ao custo de tempo de execução, gerenciamento de memória, chamadas de construções, pois o compilador não pode jogar essas informações fora: é necessário para envio dinâmico. Isso, por sua vez, nos dá a capacidade de "conversar" com o nosso programa em tempo de execução e desenvolvê-lo enquanto estiver em execução, tendo componentes refletíveis.


2
desculpe, mas algumas partes dessa "resposta" são erradas ou perigosamente enganosas. Infelizmente, o espaço para comentários é muito pequeno para listá-los todos (a maioria dos métodos não é incorporada, isso impediria o envio virtual e incharia o binário; mesmo que incorporado, pode haver uma cópia endereçável em algum lugar acessível; exemplo de código irrelevante que em o pior caso corrompe sua pilha e no melhor caso não se encaixa seus pressupostos; ...)
hoffmale

A resposta é ingênua, eu só queria expressar meu palpite sobre o motivo pelo qual construtor / destruidor não pode ser referenciado. Concordo que, no caso de classes virtuais, a vtable deve persistir e o código endereçável deve estar na memória para que a vtable possa fazer referência a ela. No entanto, as classes que não implementam uma classe virtual parecem estar embutidas, como no caso de std :: string. Nem tudo fica embutido, mas coisas que não parecem minimamente inseridas em um bloco de código "anônimo" em algum lugar da memória. Além disso, como o código corrompe a pilha? Certamente perdemos a corda, mas, caso contrário, tudo o que fizemos foi reinterpretar.
Dmitry

A corrupção de memória ocorre em um programa de computador quando o conteúdo de um local de memória é modificado acidentalmente. Este programa faz isso intencionalmente e não tenta mais usar essa sequência, portanto, não há corrupção, apenas espaço desperdiçado na pilha. Mas sim, a invariante da string não é mais mantida, ela desorganiza o escopo (no final da qual, a pilha é recuperada).
Dmitry

dependendo da implementação da string, você pode escrever sobre bytes que não deseja. Se string é algo como struct { int size; const char * data; };(como você parece presumir), você escreve 4 * 4 bytes = 16 bytes em um endereço de memória em que você só reservou 8 bytes em uma máquina x86, portanto, 8 bytes são gravados sobre outros dados (que podem corromper sua pilha ) Felizmente, std::stringnormalmente possui alguma otimização no local para cadeias curtas, portanto deve ser grande o suficiente para o seu exemplo ao usar alguma implementação principal padrão.
hoffmale

@ hoffmale você está absolutamente certo, pode ter 4 bytes, 8 ou até 1 byte. No entanto, depois de saber o tamanho da string, você também sabe que essa memória está na pilha no escopo atual e pode usá-la como desejar. Meu argumento foi que, se você conhece a estrutura, ela é compactada de maneira independente de qualquer informação sobre a classe, ao contrário dos objetos COM que possuem um uuid que identifica sua classe como parte da tabela da IUnknown. Por sua vez, o compilador acessa esses dados diretamente através de funções estáticas inlining ou mutiladas.
Dmitry
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.