有没有办法创建实例并为名称空间中的数据成员赋值?

时间:2014-07-06 14:28:36

标签: c++ opengl namespaces

为了使代码看起来干净,我在命名空间 Material 中声明了一个类 MaterialTexture 来存储OpenGL的灯光属性。我想在同一名称空间中为此类创建实例(例如 metal plastic )。 我做了一些谷歌搜索,发现a previous discussion表明数据成员不能在命名空间中分配。建议构造函数初始化数据成员。

但是,在我的案例中有12个数据成员,传递12个参数来构造实例会很繁琐。相反,我想使用三个函数(例如setDiffuse())来设置每个实例的值。

namespace Material
{
    class MaterialTexture
    {
        private:
            float diffuse[4];
            float specular[4];
            float ambient[4];
        public:
            // Three functions below set the values for data members above. 
            void setDiffuse(float R, float G, float B, float A);
            void setSpecular(float R, float G, float B, float A);
            void setAmbient(float R, float G, float B, float A);

            /* the following function tell the OpenGL how to draw the 
                Texture of this kind of Material and is not important 
                here.                                                 */
            void setMaterial();
    };
    void MaterialTexture::setDiffuse(float R, float G, float B, float A)
    {
        diffuse[0]=R; diffuse[1]=G; diffuse[2]=B; diffuse[3]=A;
    }

    // Create instances plastic and metal
    MaterialTexture plastic;
    plastic.setDiffuse(0.6, 0.0, 0.0, 1.0);
    ...

    MaterialTexture metal;
    metal.setDiffuse(...);
    ...

}; // end of namespace

因此,要创建一个类似红色塑料的球体,我只需要在显示回调中输入以下代码:

Material::MaterialTexture::plastic.setMaterial();
glutSolidSphere(...);

用g ++编译上面的代码,它给出了错误:

error: ‘plastic’ does not name a type

error: ‘metal’ does not name a type

在每个实例的三个设置函数(例如setDiffuse())的行中。

因此,它似乎不仅直接在命名空间中赋值,而且函数包含赋值是不允许的......我是对的吗?有没有其他方法来解决这个问题?或者,还有其他方法可以促进OpenGL编程吗?

2 个答案:

答案 0 :(得分:0)

你根本无法“在不知名的地方”调用一个函数。这与名称空间无关。考虑这个完整的例子:

void f() { }

f();

int main()
{
}

它不会编译。

解决方案是添加三个不同的静态成员函数,返回三个特殊实例,实际上可以在另一个(私有)静态成员函数中创建。这是一个例子:

class MaterialTexture
{
private:
    float diffuse[4];
    float specular[4];
    float ambient[4];

    static MaterialTexture makePlastic()
    {    
        MaterialTexture plastic;
        plastic.setDiffuse(0.6, 0.0, 0.0, 1.0);
        return plastic;
    }

public:
    // Three functions below set the values for data members above. 
    void setDiffuse(float R, float G, float B, float A);
    void setSpecular(float R, float G, float B, float A);
    void setAmbient(float R, float G, float B, float A);

    static MaterialTexture &plastic()
    {
        static MaterialTexture plastic = makePlastic();
        return plastic;
    }
};

但是,还有改进的余地,特别是如果你可以使用C ++ 11:

  • 将原始数组替换为std::array
  • 请考虑使用double代替float
  • 添加一个私有构造函数,它允许您删除私有静态成员函数,而是允许您直接创建对象。

以下是一个完整的改进示例:

class MaterialTexture
{
private:
    std::array<double, 4> diffuse;
    std::array<double, 4> specular;
    std::array<double, 4> ambient;

    MaterialTexture(std::array<double, 4> const &diffuse, std::array<double, 4> const &specular,
        std::array<double, 4> const &ambient) : diffuse(diffuse), specular(specular), ambient(ambient) {}
public:
    // Three functions below set the values for data members above. 
    void setDiffuse(double R, double G, double B, double A);
    void setSpecular(double R, double G, double B, double A);
    void setAmbient(double R, double G, double B, double A);

    static MaterialTexture &plastic()
    {
        static MaterialTexture plastic(
            { 0.6, 0.0, 0.0, 1.0 },
            { 0.0, 0.0, 0.0, 0.0 },
            { 0.0, 0.0, 0.0, 0.0 }
        );
        return plastic;
    }
};

答案 1 :(得分:0)

除了你的代码的直接问题(所有可执行代码必须在一个函数内,在你的例子中不是这种情况),如何最好地构造它是非常主观的。在此基础上,请阅读以下段落,并附上隐含的&#34;恕我直言&#34;。以下是我提出的一个简单的解决方案,可以帮助你。

我会坚持你的命名以保持一致。即使我不喜欢这个班级的名字MaterialTexture。在OpenGL的上下文中,该名称表明该类封装了一个纹理,但它不是。

首先,类应该具有初始化所有成员的构造函数。这确保即使未按预期使用类,也不会有任何类成员处于未初始化状态,否则可能导致未定义的行为。我同意在这种情况下使用带有12个浮点参数的构造函数会很尴尬。我会有一个默认构造函数,将所有成员初始化为合理的默认值,例如匹配旧固定管道的默认值:

MaterialTexture::MaterialTexture()
{
   diffuse[0] = 0.8f;
   diffuse[1] = 0.8f;
   diffuse[2] = 0.8f;
   diffuse[3] = 1.0f;

   ambient[0] = 0.2f;
   ambient[1] = 0.2f;
   ambient[2] = 0.2f;
   ambient[3] = 1.0f;

   specular[0] = 0.0f;
   specular[1] = 0.0f;
   specular[2] = 0.0f;
   specular[3] = 1.0f;
}

您的setDiffuse()setSpecular()setAmbient()方法看起来不错。

现在问题是您在哪里创建特定材料(plasticmetal)。在这个类中使用静态方法是一种选择,但我不认为它是一个干净的设计。此类表示通用材料。将特定材料的知识放在同一个类中会混淆应该分开的责任。

我会有一个单独的类,提供特定的材料。关于如何设计和结构化有很多选择。最简单的方法是创建特定材料的方法。对于允许您在不更改任何接口的情况下添加材料的更好的方法,您可以按名称引用它们:

class MaterialLibrary
{
public:
    MaterialLibrary();

    const MaterialTexture& getMaterial(const std::string& materialName) const;

private:
    std::map<std::string, MaterialTexture> m_materials;
}

MaterialLibrary::MaterialLibrary()
{
    MaterialTexture plastic;
    plastic.setDiffuse(...);
    ...
    m_materials.insert(std::make_pair("plastic", plastic));

    MaterialTexture metal;
    metal.setDiffuse(...);
    ...
    m_materials.insert(std::make_pair("metal", metal));
}

const MaterialTexture& MaterialLibrary::getMaterial(const std::string& materialName) const
{
    return m_materials.at(materialName);
}

您可以轻松更改此选项以从配置文件中读取材料列表,而不是在代码中将其包含在内。

现在您只需要创建并使用MaterialLibrary的一个实例。同样,有多种方法可以做到这一点。你可以把它变成一个单身人士。或者在启动期间创建一个实例,并将该实例传递给需要它的每个人。

获得MaterialLibrary实例lib后,您现在可以获取材料:

const MaterialTexture& mat = lib.getMaterial("plastic");

你可以变得更加漂亮并使MaterialLibrary更多的工厂类,例如模板工厂。这将进一步分离责任,MaterialLibrary仅维护材料清单,并提供对它们的访问,但不知道如何构建可用的特定材料列表。您决定在抽象中走多远就是您的决定。