Existe um uso legítimo para void *?

87

Existe um uso legítimo de void*em C ++? Ou foi introduzido porque C o tinha?

Só para recapitular meus pensamentos:

Entrada : Se quisermos permitir vários tipos de entrada, podemos sobrecarregar funções e métodos, alternativamente, podemos definir uma classe base comum, ou modelo (obrigado por mencionar isso nas respostas). Em ambos os casos, o código é mais descritivo e menos sujeito a erros (desde que a classe base seja implementada de maneira sã).

Saída : Não consigo pensar em nenhuma situação em que preferiria receber void*em vez de algo derivado de uma classe base conhecida.

Apenas para deixar claro o que quero dizer: não estou perguntando especificamente se há um caso de uso para void*, mas se há um caso em que void*é a melhor ou a única escolha disponível. O que foi perfeitamente respondido por várias pessoas abaixo.

magu_
fonte
3
que tal quando você deseja ter vários tipos como int & std :: string?
Amir
12
@Amir, variant, any, marcado união. Qualquer coisa que possa indicar o tipo real de conteúdo e ser mais seguro de usar.
Revolver_Ocelot
15
"C tem" é uma justificativa forte o suficiente, não há necessidade de procurar por mais. Evitá-lo tanto quanto possível é uma coisa boa em qualquer um dos idiomas.
n. 'pronomes' m.
1
Só uma coisa: a interoperabilidade com APIs de estilo C é estranha sem ela.
Martin James
1
Um uso interessante é o apagamento de tipo para vetores de ponteiros
Paolo M

Respostas:

81

void*é pelo menos necessário como resultado de ::operator new(também todo operator new...) e de malloce como o argumento do newoperador de colocação .

void*pode ser considerado o supertipo comum de todo tipo de ponteiro. Portanto, não significa exatamente um ponteiro para void, mas um ponteiro para qualquer coisa.

BTW, se você quiser manter alguns dados para várias variáveis ​​globais não relacionadas, você pode usar alguns std::map<void*,int> score; então, depois de ter declarado global int x;e double y;e std::string s;fazer score[&x]=1;e score[&y]=2;escore[&z]=3;

memset quer um void*endereço (os mais genéricos)

Além disso, os sistemas POSIX têm dlsym e seu tipo de retorno evidentemente deve servoid*

Basile Starynkevitch
fonte
1
Esse é um ponto muito bom. Esqueci de mencionar que estava pensando mais no lado do usuário da linguagem do que na implementação. Mais uma coisa que não entendo: nova é uma função? Pensei nisso mais como uma palavra
magu_
9
new é um operador, como + e sizeof.
Josué
7
É claro que a memória também pode ser retornada por meio de um char *. Eles são muito próximos nesse aspecto, embora o significado seja um pouco diferente.
edmz
@Joshua. Não sabia disso. Obrigado por me ensinar isso. Uma vez que C++está escrito, C++isso é motivo suficiente para ter void*. Tenho que amar este site. Faça uma pergunta e aprenda muito.
magu_
3
@black Nos velhos tempos, C nem tinha um void*tipo. Todas as funções de biblioteca padrão usadaschar*
Cole Johnson
28

Existem vários motivos para usar void*, sendo os 3 mais comuns:

  1. interagir com uma biblioteca C usando void*em sua interface
  2. apagamento de tipo
  3. denotando memória não digitada

Na ordem inversa, denotar memória não digitada com void*(3) em vez de char*(ou variantes) ajuda a evitar aritmética acidental de ponteiro; existem muito poucas operações disponíveis no, void*por isso geralmente requer conversão antes de ser útil. E, claro, assim como com, char*não há problema com aliasing.

O apagamento de tipo (2) ainda é usado em C ++, em conjunto com modelos ou não:

  • código não genérico ajuda a reduzir o inchaço binário, é útil em caminhos frios, mesmo em código genérico
  • código não genérico é necessário para armazenamento às vezes, mesmo em contêineres genéricos, como std::function

E obviamente, quando a interface com a qual você lida usa void*(1), você tem pouca escolha.

Matthieu M.
fonte
15

Ai sim. Mesmo em C ++, às vezes optamos por, em void *vez de template<class T*>porque às vezes o código extra da expansão do template pesa muito.

Normalmente, eu o usaria como a implementação real do tipo, e o tipo de modelo herdaria dele e envolveria os casts.

Além disso, os alocadores de blocos personalizados (novas implementações do operador) devem ser usados void *. Esta é uma das razões pelas quais o g ++ adicionou uma extensão para permitir a aritmática do ponteiro void *como se fosse de tamanho 1.

Joshua
fonte
Obrigado pela sua resposta. Você está certo, esqueci de mencionar os modelos. Mas não seria templates ser ainda mais adequado para a tarefa desde o raramente introduzir uma penalidade de desempenho (? Stackoverflow.com/questions/2442358/... )
magu_
1
Os modelos introduzem uma penalidade no tamanho do código, que também é uma penalidade de desempenho na maioria dos casos.
Joshua
struct wrapper_base {}; template<class T> struct wrapper : public wrapper_base {T val;} typedef wrapper* like_void_ptr;é um simulador void - * - mínimo usando modelos.
user253751
3
@Joshua: Você vai precisar de uma citação para a "maioria".
user541686
@Mehrdad: Veja cache L1.
Josué
10

Entrada: se quisermos permitir vários tipos de entrada, podemos sobrecarregar funções e métodos

Verdadeiro.

alternativamente, podemos definir uma classe base comum.

Isso é parcialmente verdade: e se você não puder definir uma classe base comum, uma interface ou algo semelhante? Para defini-los, você precisa ter acesso ao código-fonte, o que muitas vezes não é possível.

Você não mencionou modelos. No entanto, os modelos não podem ajudá-lo com polimorfismo: eles funcionam com tipos estáticos, ou seja, conhecidos em tempo de compilação.

void*pode ser considerado como o menor denominador comum. Em C ++, você normalmente não precisa dele porque (i) você não pode fazer muito com ele inerentemente e (ii) quase sempre há soluções melhores.

Além disso, você acabará convertendo-o em outros tipos de concreto. É por isso que char *geralmente é melhor, embora possa indicar que você está esperando uma string de estilo C, em vez de um bloco puro de dados. É por isso que void*é melhor do que char*isso, porque permite a conversão implícita de outros tipos de ponteiro.

Você deve receber alguns dados, trabalhar com eles e produzir uma saída; para conseguir isso, você precisa conhecer os dados com os quais está trabalhando, caso contrário, você terá um problema diferente que não é o que estava resolvendo originalmente. Muitos idiomas não têm void*e não têm nenhum problema com isso, por exemplo.

Outro uso legítimo

Ao imprimir endereços de ponteiro com funções como printfo ponteiro deve ter void*tipo e, portanto, você pode precisar de um lançamento para void*

edmz
fonte
7

Sim, é tão útil quanto qualquer outra coisa na língua.
Como exemplo, você pode usá-lo para apagar o tipo de classe que você pode converter estaticamente para o tipo certo quando necessário, a fim de ter uma interface mínima e flexível.

Em que a resposta não é um exemplo de uso que deve dar-lhe uma idéia.
Eu copio e colo abaixo por uma questão de clareza:

class Dispatcher {
    Dispatcher() { }

    template<class C, void(C::*M)() = C::receive>
    static void invoke(void *instance) {
        (static_cast<C*>(instance)->*M)();
    }

public:
    template<class C, void(C::*M)() = &C::receive>
    static Dispatcher create(C *instance) {
        Dispatcher d;
        d.fn = &invoke<C, M>;
        d.instance = instance;
        return d;
    }

    void operator()() {
        (fn)(instance);
    }

private:
    using Fn = void(*)(void *);
    Fn fn;
    void *instance;
};

Obviamente, este é apenas um dos vários usos do void*.

skypjack
fonte
4

Interface com uma função de biblioteca externa que retorna um ponteiro. Aqui está um para um aplicativo Ada.

extern "C" { void* ada_function();}

void* m_status_ptr = ada_function();

Isso retorna um ponteiro para tudo o que Ada queria falar para você. Você não precisa fazer nada sofisticado com ele, você pode devolvê-lo a Ada para fazer a próxima coisa. Na verdade, desemaranhar um ponteiro Ada em C ++ não é trivial.

RedSonja
fonte
Não poderíamos usar autoem vez disso neste caso? Supondo que o tipo seja conhecido em tempo de compilação.
magu_
Ah, hoje em dia provavelmente poderíamos. Esse código é de alguns anos atrás, quando fiz alguns wrappers Ada.
RedSonja
Os tipos Ada podem ser diabólicos - eles fazem os seus próprios, você sabe, e têm um prazer perverso em torná-los complicados. Eu não tive permissão para mudar a interface, isso teria sido muito fácil, e ele retornou algumas coisas desagradáveis ​​escondidas naqueles ponteiros vazios. Ugh.
RedSonja
2

Resumindo, C ++ como uma linguagem estrita (sem levar em conta relíquias C como malloc () ) requer void *, uma vez que não tem pai comum de todos os tipos possíveis. Ao contrário do ObjC, por exemplo, que tem objeto .

Nredko
fonte
malloce newambos retornam void *, então você precisaria mesmo que houvesse uma classe de objeto em C ++
Dmitry Grigoryev
malloc é relict, mas em linguagens estritas, new deve retornar o objeto *
nredko
como você aloca uma matriz de inteiros então?
Dmitry Grigoryev
@DmitryGrigoryev operator new()retorna void *, mas a newexpressão não
MM
1. Não tenho certeza se gostaria de ver um objeto de classe base virtual acima de cada classe e tipo , incluindo int2. se for EBC não virtual, em que difere void*?
Lorro
1

A primeira coisa que me ocorre (que eu suspeito que seja um caso concreto de algumas das respostas acima) é a capacidade de passar uma instância de objeto para um threadproc no Windows.

Eu tenho algumas classes C ++ que precisam fazer isso, elas têm implementações de thread de trabalho e o parâmetro LPVOID na API CreateThread () obtém um endereço de uma implementação de método estático na classe para que a thread de trabalho possa fazer o trabalho com uma instância específica da classe. A conversão estática simples de volta no threadproc produz a instância com a qual trabalhar, permitindo que cada objeto instanciado tenha um thread de trabalho a partir de uma única implementação de método estático.

AndyW
fonte
0

Em caso de herança múltipla, se você precisa para obter um ponteiro para o primeiro byte de um pedaço de memória ocupada por um objeto, você pode dynamic_castpara void*.

Ameaça Menor
fonte