Richiamo di un metodo statico dall'elenco di inizializzazione dei membri del costruttore

1

Sto implementando un semplice sistema di rendering per un motore di gioco. Nel mio motore ho delle entità renderizzabili che hanno un componente Model (sto usando l'ereditarietà al posto di un ECS per il mio motore per ora, ma incapsulare un comportamento comune nei componenti renderà le cose più facili quando / se voglio transistionare in un componente basato architettura). Questa classe Model contiene una mesh (buffer di vertici e buffer di indice), trame e materiali.

Alcuni modelli di entità sono caricati da file (modelli 3D generati da applicazioni di modellazione), quindi passo semplicemente un modello nei loro costruttori, mentre altri hanno attributi di mesh e di vertici che sono "cablati" nella loro classe. Ad esempio una classe SkyBox dovrebbe essere codificata in questo modo:

// SkyBox.hpp

class SkyBox 
{
public:
    SkyBox(const wchar_t *cubeMapFilePath);
    ~SkyBox();
    XMFLOAT4X4 const &GetWorldMatrix() const;
    Model const &GetModel() const;
    void SetRotation(XMFLOAT3 const &rotation);
    static Model CreateSkyBox(const wchar_t *cubeMapFilePath);   // static member function called from constructor
private: 
    Model mModel;                                                // the member with no def-ctor
    XMFLOAT3 mRotation;
    mutable XMFLOAT4X4 mWorldMatrix;
    mutable bool mDirtyFlag = true;
};


// SkyBox.cpp

#include "SkyBox.h"

SkyBox::SkyBox(const wchar_t *cubeMapFilePath) : mModel(CreateSkyBox(cubeMapFilePath)), mRotation(0.0f, 0.0f, 0.0f)
{
}

SkyBox::~SkyBox()
{
}

Model SkyBox::CreateSkyBox(const wchar_t *cubeMapFilePath)
{
    Mesh mesh;
    std::vector<XMFLOAT3> positions;
    positions.push_back(XMFLOAT3(-0.5f, 0.5f, -0.5f));
    positions.push_back(XMFLOAT3(0.5f, 0.5f, -0.5f));
    positions.push_back(XMFLOAT3(0.5f, -0.5f, -0.5f));
    positions.push_back(XMFLOAT3(-0.5f, -0.5f, -0.5f));
    positions.push_back(XMFLOAT3(-0.5f, 0.5f, 0.5f));
    positions.push_back(XMFLOAT3(0.5f, 0.5f, 0.5f));
    positions.push_back(XMFLOAT3(0.5f, -0.5f, 0.5f));
    positions.push_back(XMFLOAT3(-0.5f, -0.5f, 0.5f));

    std::vector<unsigned int> indices{ 0, 1, 3, 3, 1, 2, 5, 4, 6, 6, 4, 7, 4, 0, 7, 7, 0, 3, 1, 5, 2, 2, 5, 6, 4, 5, 0, 0, 5, 1, 3, 2, 7, 7, 2, 6 };

    mesh.LoadAttribute("POSITION", &positions[0], positions.size());
    mesh.LoadIndexBuffer(indices);

    std::vector<Texture> cubeMap;
    Texture texture(Texture::CUBE_MAP);
    texture.LoadCubeMap(cubeMapFilePath);
    cubeMap.push_back(texture);

    Material material = {};

    return Model(mesh, cubeMap, material);
}

//    .... other members

La mia classe Model non ha un costruttore predefinito, quindi quando definisco una classe che ha un componente model devo fornire un modo per costruirne uno nel costruttore della classe dell'entità, ma per costruire un modello ho bisogno prima di eseguire alcuni calcoli (posizioni dei vertici calcolati, normali ecc.).

Mi è venuta in mente questa soluzione, chiamando una funzione membro statico (privata) che esegue i calcoli necessari e restituisce un modello, con cui inizializzare il membro del modello nella classe. Un'alternativa sarebbe fornire alla classe Model un costruttore predefinito ed eseguire i calcoli nel costruttore dell'entità e quindi assegnarlo al membro del modello, ma non ha molto senso avere un modello vuoto.

Quindi, questa soluzione è un hack o è buona, o esiste un modello per questo tipo di problemi, o dovrei andare per il costruttore predefinito?

In realtà, dovevo assegnare alla classe Model un costruttore predefinito in quanto dovevo accedere ai membri dei dati durante la costruzione di modelli per altre classi (una classe Terrain, ad esempio, memorizza le altezze della griglia in una matrice membro dati per il rilevamento delle collisioni). Non so se è il modo giusto per farlo, ma è veloce e sporco e funziona.

    
posta Luca 17.07.2018 - 17:36
fonte

2 risposte

4

Hai detto nel tuo testo che CreateSkyBox () era privato, ma non dichiarato privato.

Non c'è assolutamente nulla di sbagliato nell'avere funzioni di "helper" statiche in una classe, che sono usate per aiutare con qualsiasi cosa abbia senso - inclusa la creazione di oggetti ausiliari come il Modello.

Se non ha mai senso avere un modello vuoto, allora non dovrebbe avere un costruttore senza argomenti (esattamente come hai fatto tu).

Anche se penso che il tuo codice / approccio sia soddisfacente, c'è un altro modello che puoi usare per circostanze simili. Se si desidera avere mModel in uno stato non strutturato (null), utilizzare facoltativo anziché Model; Quindi puoi costruirlo all'interno del corpo del tuo costruttore. Nel tuo caso particolare, credo che quello che hai fatto sia migliore, ma se i tuoi bisogni cambiano, e devi essere in grado di costruire alcuni oggetti (come Skybox) quando non hai ancora tutti i dati (quando leggi oggetti da un file è un caso comune in cui ciò può accadere), quindi l'utilizzo di std :: facoltativo può esservi d'aiuto.

    
risposta data 17.07.2018 - 18:12
fonte
1

Potrebbe essere aperto a chiedersi se sia migliore , ma un'altra possibilità da considerare sarebbe se si possa equipaggiare Model con un costruttore che costruisce il Modello in modo appropriato, quindi si inietta quella dipendenza nel SkyBox:

class Model {
public:
    Model(Mesh const &mesh, Texture const &texture, Material const &mat);
};

class SkyBox {
public:
    SkyBox(Model const &m) : mModel(Model(input_data)) {}
    // ...
};

Non ho intenzione di affermare che questo è necessariamente il modo giusto di fare le cose. In particolare, se un modello è principalmente un'aggregazione di un gruppo di dati, questo potrebbe non essere affatto utile - ma ci sono certamente casi in cui può essere utile.

    
risposta data 18.07.2018 - 17:30
fonte

Leggi altre domande sui tag