O Vector3 deve herdar do Vector2?

18

Estou criando um par de classes Vector2(X & Y) e Vector3(X, Y & Z), mas eu não sei se para fazer Vector3herdar a partir de Vector2, ou se a re implementar-as variáveis de membro m_xe m_yde novo? Quais são os prós e os contras de cada lado (herança versus redefinição).

Edit: Estou usando C ++ (VS2010).

Mark Ingram
fonte
11
Por que não escrever uma classe vetorial geral para n vetores dimensionais e depois (se necessário) herdar uma classe vector2 e vector3. Você também pode usar modelos para a classe geral e herdar versões para vetores inteiros e vetores flutuantes também. Edit: Por que você não usa uma biblioteca matemática otimizada?
31512 danijar
5
Por um triz da imaginação "Vector3 é um Vector2", eles poderiam herdar de um VectorN pai
wim
11
Sim, mas você provavelmente precisará de uma tabela virtual e esse é um dos casos em que os custos de tempo de execução e memória podem ser importantes. Idealmente, a Vector3deve ser apenas 3 no floatsque diz respeito à memória. Não estou dizendo que é impossível, só que eu nunca vi isso em um mecanismo de produção.
Laurent Couvidou 17/10/12
2
Sim, acho que sim. Até enquanto você não precisar de mais nada floats. Você sabe, YAGNI, KISS, todas essas coisas. Vector2, Vector3e Vector4sem herança e floatsapenas é realmente o padrão de fato nos mecanismos de jogo.
Laurent Couvidou
11
Espero que você quis dizer typedef float real;;).
Mark Ingram

Respostas:

47

Não, não deveria. A única coisa que você estaria usando da herança é o xe ycomponentes. Os métodos usados ​​em uma Vector2classe não seriam úteis em uma Vector3classe, provavelmente usariam argumentos diferentes e executariam operações em um número diferente de variáveis-membro.

MichaelHouse
fonte
+1, devo prestar mais atenção ao pop-up para não escrever coisas redundantes.
Matsemann 16/10/12
8
Uso excessivo de herança clássica . Um Vector3IS-NOT-A Vector2(por isso não deve herdar), mas um AppleIS-A Fruit(por isso pode herdar). Se você torce a cabeça o suficiente, Vector3possui um HAS-A Vector2, mas a perda de desempenho e a dificuldade de codificação significa que você escreverá classes completamente separadas para Vector3e Vector2.
bobobobo
Mas você pode (na minha opinião deveria) escrever uma classe de vetor n-dimensional para herdar um vetor 2D e um vetor 3D.
danijar
8

Há uma coisa curiosa que você pode fazer com C ++ (você não especificou uma linguagem, e essa resposta é principalmente porque acho bom ver alternativas, embora eu realmente não acredite que isso seja útil na maioria dos casos.)

Usando modelos, você pode fazer algo assim:

template <class T, class S, int U>
class VectorN
{
    protected:
        int _vec[U];
    public:
        S& operator+=(const S c)
        {
            for(int i = 0; i < U; i++)
            {
                _vec[i] += c.at(i);
            }
            return (S&)*this;
        }
        int at(int n) const
        {
            return _vec[n];
        }
};

template <class T>
class Vec2 : public VectorN<T,Vec2<T>,2>
{
    public:
        T& x;
        T& y;
        Vec2(T a, T b) : x(this->_vec[0]), y(this->_vec[1])
        {
            this->_vec[0] = a;
            this->_vec[1] = b;
        }
};

template <class T>
class Vec3 : public VectorN<T,Vec3<T>,3>
{
    public:
        T& x;
        T& y;
        T& z;
        Vec3(T a, T b, T c) : x(this->_vec[0]), y(this->_vec[1]), z(this->_vec[2])
        {
            this->_vec[0] = a;
            this->_vec[1] = b;
            this->_vec[2] = c;
        }
};

e isso pode ser usado assim:

int main(int argc, char* argv[])
{

    Vec2<int> v1(5,0);
    Vec2<int> v2(10,1);

    std::cout<<((v1+=v2)+=v2).x;
    return 0;
}

Como eu disse, não acho que isso seja útil e provavelmente complicará sua vida quando você tentar implementar pontos / normalizar / outras coisas e tentar ser genérico com qualquer número de vetores.

Luke B.
fonte
Sim, toda a genericness parece bom, só a maioria das vezes você só precisa de um 3-vetor padrão com 3 componentes de ponto flutuante - todos os colchetes vai fazer Vector3f vum pouco mais bloatyVector3<float> v
bobobobo
@bobobobo Sim, eu concordo. Minhas classes de vetor são geralmente vec2 e vec3 sem pai, mas ainda os tornam modelos. Se escrever Vector3 <float> incomoda você, você sempre pode digitá-lo #
Luke B.
..E agora o argumento do programador C .. "mas e quanto maior o tempo de compilação para usar modelos?" Realmente vale a pena neste caso?
bobobobo
@obobobo Eu nunca tive nenhum problema com meus tempos de compilação: P, mas nunca trabalhei em um projeto que o tempo de compilação seria um problema. Pode-se argumentar que os tempos de compilação justificam não usar flutuadores quando você precisa de números inteiros.
Luke B.
@obobobo Com instanciações explícitas e não incluindo seu arquivo embutido no cabeçalho, os tempos de compilação não serão diferentes. Além disso, o inchaço do suporte angular do modelo está a apenas uma typedefdistância.
Samaursa
7

Independentemente da velocidade, a primeira pergunta que você deve fazer a si mesmo ao fazer uma herança é se vai usá-las polimorficamente. Mais especificamente, existe alguma situação em que você pode se ver usando a Vector3como se fosse um Vector2(que, herdando dela, está explicitamente dizendo que um vetor3 "é-um" vetor2).

Caso contrário, você não deve usar herança. Você não deve usar herança para compartilhar código. É para isso que servem os componentes e funções externas, não que você esteja compartilhando qualquer código entre eles de qualquer maneira.

Dito isto, você pode querer maneiras fáceis de converter Vector3 s para Vector2s e, nesse caso, pode escrever uma sobrecarga de operador que truncará implicitamente o Vector3para a Vector2. Mas você não deve herdar.

Tetrad
fonte
Obrigado, eu acho que isso destacou o problema, eu estava analisando isso do ponto de vista do "compartilhamento de código" (ou seja, sem precisar "digitar novamente" os valores X e Y).
Mark Ingram
+1 ótima resposta, não há uso polimórfico entre vetores de tamanhos diferentes.
Luke B.
Essa é a maior coisa que eu adicionaria à minha própria resposta - +1 com certeza. (Embora existam circunstâncias estranhas nas quais eu possa imaginar querer polimorfismo - por exemplo, jogos 2.5d 'mapas de altura' em que coisas como verificações de distância, percursos etc., canonicamente desejem ser feitas em 2D, mas você ainda precisa fornecer coordenadas 3D para objetos)
Steven Stadnicki 17/10/12
@LukeB. Embora no caso dos OPs eu concorde que parece que não há razão para herdar, Vector2mas herdar de uma base Vector<N>? Isso faz todo o sentido. Além disso, por que herança significa automaticamente comportamento polimórfico? Uma das melhores coisas do C ++ é que você pode ter uma herança de custo zero. Não há necessidade de adicionar quaisquer métodos virtuais (incluindo destruidores virtuais) na base de Vector<N>classe.
Samaursa
5

Não, já que todos os métodos também precisam ser substituídos, você não poderá herdar dele.

Se alguma coisa, ambos poderiam implementar uma interface Vector. No entanto, como você provavelmente não deseja adicionar / sub / dot / dst entre um Vector2 e Vector3, isso terá efeitos colaterais indesejados. E ter parâmetros diferentes, etc., seria um aborrecimento.
Então, eu realmente não consigo ver nenhum profissional de herança / interface neste caso.

Um exemplo é a estrutura Libgdx, onde Vector2 e Vector3 não têm nada a ver um com o outro, além de ter o mesmo tipo de métodos.

Matsemann
fonte
2

Se você planeja usar matrizes SIMD, provavelmente é o melhor. Se você ainda deseja sobrecarregar o operador, considere usar uma interface / mixin para acessar a matriz subjacente - por exemplo, aqui está um ponto de partida que possui apenas o (não testado) Add.

Observe como eu não forneci X/ Y/ Z, cada VectorXclasse herdaria diretamente dessa - pelas mesmas razões especificadas por outras pessoas. Ainda assim, eu vi matrizes usadas como vetores muitas vezes na natureza.

#include <xmmintrin.h>

class Vector
{
public:
    Vector(void)
    {
        Values = AllocArray();
    }

    virtual ~Vector(void) 
    { 
        _aligned_free(Values);
    }

    // Gets a pointer to the array that contains the vector.
    float* GetVector()
    {
        return Values;
    }

    // Gets the number of dimensions contained by the vector.
    virtual char GetDimensions() = 0;

    // An example of how the Vector2 Add would look.
    Vector2 operator+ (const Vector2& other)
    {
        return Vector2(Add(other.Values));
    }

protected:
    Vector(float* values)
    {
        // Assume it was created correctly.
        Values = values;
    }

    // The array of values in the vector.
    float* Values;

    // Adds another vector to this one (this + other)
    float* Add(float* other)
    {
        float* r = AllocArray();

#if SSE
        __m128 pv1 = _mm_load_ps(Values);
        __m128 pv2 = _mm_load_ps(other);
        __m128 pvr = _mm_load_ps(r);

        pvr = _mm_add_ps(pv1, pv2);
        _mm_store_ps(r, pvr);

#else
        char dims = GetDimensions();
        for(char i = 0; i < dims; i++)
            r[i] = Values[i] + other[i];
#endif

        return r;
    }

private:

    float* AllocArray()
    {
        // SSE float arrays need to be 16-byte aligned.
        return (float*) _aligned_malloc(GetDimensions() * sizeof(float), 16);
    }
};

Isenção de responsabilidade: Meu C ++ pode ser uma droga, já faz um tempo desde que eu o usei.

Jonathan Dickinson
fonte
Espere cara , o seu uso _aligned_mallocsignifica que o bug que eu abri não é realmente um bug?
bobobobo
Você não deve usar projeções de ponteiro para inserir seus valores no __m128registro, mas _mm_loadu_pssim usar . Uma classe boa amostra é aqui em "vectorclass.zip"
bobobobo
@obobobo Eu farei a melhor tentativa de uma edição - tome especial atenção ao aviso;).
11136 Jonathan Dickinson
O @obobobo _mm_loadu_psdeve funcionar para você com essa estrutura (onde _mm_load_psnão será). Também adicionei sua sugestão - fique à vontade para editar a pergunta se achar que estou latindo na árvore errada (já faz um tempo desde que usei C [++]).
11136 Jonathan Dickinson
1

Outro golpe sério em ter o Vec3 herdado do Vec2 ou, sem dúvida, em herdar os dois de uma única classe Vector: seu código estará fazendo muitode operações em vetores, geralmente em situações críticas, e é de seu interesse garantir que todas essas operações sejam o mais rápidas possível - muito mais do que é para muitos outros objetos que não são tão universal ou de baixo nível. Enquanto um bom compilador fará o possível para aplanar qualquer sobrecarga de herança, você continuará confiando mais no compilador do que gostaria; em vez disso, eu os construíria como estruturas com o mínimo de sobrecarga possível e possivelmente tentaria e faria com que a maioria das funções que as usassem (com exceção de coisas como operador + que realmente não podem ser ajudadas) fossem globais em vez de métodos no struct. Geralmente, a otimização antecipada é recomendada contra, e por um excelente motivo,

Steven Stadnicki
fonte
11
-1 porque: class e struct só têm implicações de concessão de acesso em C ++ e o OP não especificou um idioma, funções-membro não virtuais têm as mesmas implicações de desempenho que funções não-membros e funções-membro virtuais (que potencialmente exibem os problemas com os quais você se preocupa) só existem se você os fizer, não simplesmente utilizando a herança.
2
@JoshPetrie Pontos válidos em todas as frentes; Eu tendem a 'padrão' para C / C ++ e por isso vi a pergunta através dessa lente. Eu não acredito que há exercício legítimo (bem como conceitual) razões para não ir a rota de herança, você mente, mas eu poderia ter sido muito melhor sobre os detalhes específicos. Vou tentar revisitar isso e ver se posso dar uma contabilidade melhor.
Steven Stadnicki