Идиома именованных параметров и (абстрактные) базовые классы - PullRequest
0 голосов
/ 20 октября 2018

Предположим, я пишу 3D-рендерер на C ++ 11, где я создаю материалы и присваиваю их модели.

Я хотел бы иметь возможность создавать материалы с использованием идиома именованных параметров,как это:

auto material = Material().color( 1, 0, 0 ).shininess( 200 );

Затем я могу создать модель, подобную этой:

auto model = Model( "path/to/model.obj", material );

Я также хочу иметь возможность передавать материал как значение r:

auto model = Model( "path/to/model.obj", Material() );

В этом простом сценарии использования я могу написать свой класс Model следующим образом:

class Model {
  public:
    Model() = default;
    Model( const fs::path &path, const Material &material )
      : mPath( path ), mMaterial( material ) {}

  private:
    fs::path mPath;
    Material mMaterial;
};

Вопрос

Я хочу сделать Material абстрактным базовым классом,так что я могу создавать собственные реализации для определенных видов материалов.Это означает, что в моем Model вместо этого я должен хранить указатель (или даже ссылку) на материал.Но тогда я больше не могу передавать материал как r-значение.

Я мог бы вместо этого использовать переменную-член std::shared_ptr<Material>, но тогда становится намного сложнее использовать идиому именованного параметра, потому что как быЯ создаю материал в этом случае?

Кто-нибудь из вас может дать мне хороший совет?

Более подробный пример кода

class Material {
  public:
    virtual ~Material() = default;
    virtual void generateShader() = 0;
    virtual void bindShader() = 0;
};

class BasicMaterial : public Material {
  public:
    BasicMaterial() = default;
    BasicMaterial( const BasicMaterial &rhs ) = default;

    static std::shared_ptr<BasicMaterial> create( const BasicMaterial &material ) {
      return std::make_shared<BasicMaterial>( material );
    }

    void generateShader() override;
    void bindShader() override;

    BasicMaterial& color( float r, float g, float b ) {
      mColor.set( r, g, b );
      return *this;
    }

  private:
    Color mColor;
};

class Model {
  public:
    Model() = default;
    Model( const fs::path &path, ...what to use to pass Material?... )
      : mPath( path ), mMaterial( material ) {}

  private:
    Material  mMaterial; // Error! Can't use abstract base class as a member.
    Material* mMaterial; // We don't own. What if Material is destroyed?
    Material& mMaterial; // Same question. Worse: can't reassign.

    std::shared_ptr<Material> mMaterial; // This? But how to construct?    
};

// Can't say I like this syntax much. Feels wordy and inefficient.
std::shared_ptr<Material> material = 
  BasicMaterial::create( BasicMaterial().color( 1, 0, 0 ) );

auto model = Model( "path/to/model.obj", 
  BasicMaterial::create( BasicMaterial().color( 0, 1, 0 ) );

Ответы [ 2 ]

0 голосов
/ 20 октября 2018

Другим способом было бы добавить класс MaterialHolder, который бы имел shared_ptr в качестве члена.

// *** Not tested ***
class MaterialHolder
{
public:
    template <typename T> T &create() 
{ 
        T *data = new T;
        material.reset(data); 
        return *data; 
}

private:
    std::shared_ptr<Material> material;
};

Использование:

MaterialHolder x;
x.create<BasicMaterial>().color(1, 0, 0);

И вы могли бы сделать несколькоВарианты типа:

  • Наличие NullMaterial для обработки случая, когда create не вызывается.
  • Бросьте исключение, если вы попытаетесь использовать материал, который не был создан.
  • Переименуйте MaterialHolder в MaterialFactory и используйте этот класс только для целей создания (и используйте функцию для перемещения указателя).
  • Используйте unique_ptr вместо.

Или вы всегда можете написать код от руки:

auto m1 = std::make_shared<BasicMaterial>();

(*m1)
    .color(1, 0, 0)
    .shininess(200)
    ;
0 голосов
/ 20 октября 2018

На самом деле, я думаю, что почти ответил на свой вопрос в подробном примере кода.Подход shared_ptr<Material> работает хорошо, кроме своей конструкции.Но я мог бы написать следующую вспомогательную функцию, чтобы исправить это:

namespace render {

template<class T>
std::shared_ptr<T> create( const T& inst ) {
  return std::make_shared<T>( inst );
}

}

И теперь я могу создать материал и модель, подобную этой:

auto model = Model( "path/to/model.obj", create( BasicMaterial().color( 0, 0, 1 ) );

Это читабельно, понятно и не слишкомwordy.

Конечно, я все равно хотел бы услышать другие идеи или мнения.

...