0

My program has a Car and CarManager class that looks similar to the following:

#include <list>

class Car
{
public:
    void Draw() { Draw(m_opacity); }
    void Draw(float opacity)
    {
    }

private:
    float m_opacity;
};

class CarManager
{
public:
    //Draw cars using their m_opacity member
    void DrawCars() 
    { 
        for(auto i = m_cars.begin(); i != m_cars.end(); i++)
            i->Draw();
    }

    //Draw cars using opacity argument
    void DrawCars(float opacity)
    {
        for(auto i = m_cars.begin(); i != m_cars.end(); i++)
            i->Draw(opacity);
    }

private:
    std::list<Car> m_cars;
}

MyApplication::OnRender()
{
    CarManager* pCarManager = GetCarManager();

    //If this condition is met, I want all cars to be drawn with 0.5 opacity.
    if(condition)
        pCarManager->DrawCars(0.5f);

    //Otherwise, draw cars using their m_opacity value.     
    else
        pCarManager->DrawCars();
}

C++ doesn't allow a non-static member to be used as a default argument, so I have overloaded the Drawing functions. If no argument is supplied, an overloaded version of the function will be called using the class member.

Each car has an m_opacity member which is used for rendering. However, there are certain situations where I would like to specify a value for the opacity that I would like all cars to use. In these cases, I would like m_opacity to be ignored in favor of the value that I provide.

In this example, the rendering code in CarManager::DrawCars() is rather small so repeating the same code with a different call to Car::Draw() isn't a big deal. But in my actual program, repeating all of the same code is not practical.

This is starting to get messy. Is there a better way to go about this?

2 Answers 2

2

A simple way would be to use a magic value:

#include <list>

class Car
{
public:
    static float noOpacity() { return -1; }

    void Draw(float opacity)
    {
        if (opacity==noOpacity()) {
            opacity = m_opacity;
        }
        // etc.
    }

private:
    float m_opacity;
};

class CarManager
{
public:
    //Draw cars using optional opacity argument
    void DrawCars(float opacity = Car::noOpacity(); )
    {
        for(auto i = m_cars.begin(); i != m_cars.end(); i++)
            i->Draw(opacity);
    }

private:
    std::list<Car> m_cars;
}
Sign up to request clarification or add additional context in comments.

4 Comments

opacity==noOpacity() is not a good idea. use something like ABS(opacity - noOpacity()) < 0.000001 or somehting...
@Deamonpog: In what situation do you think it would cause a problem?
because its a floating point. but maybe it will work because -1 can be represented exactly as it is, in the floating point representation. also we can use something like "less than 0" because we know that opacity may vary from 0 to 100.
If it needs to be efficient i may suggest this trick that came into my mind. lets say the number goes from 0.00 to 100.00. So we know that we are using only two decimal points. so we can just use integers and the number should go from 0 to 10000. (e.g. 46.52 is represented as 4652) but this can cause trouble if you are frequently dividing the number by 100 to get the exact value between 0 to 100. otherwise it would be better. just a suggestion. :)
0

There are several ways to deal with the problem:

  1. As was pointed out in another answer, you could use a special value to indicate that a default should be used. If the data type supports an obvious special value this may be the right solution but it doesn't work with all types and is actually relatively hard to maintain. I have seen cases in production code where the "impossible" value eventually became possible, resulting in undefined behavior (e.g. yields for bonds were thought to be always positive but it turns out that bonds can have negative yields).
  2. Using an optional<T> which basically bundles a T with an indication on whether the object is actually present deals with one special value. If the choice is, indeed, binary (use the passed opacity or the object's one), this can work: the default would use an optional<T> indicating that the optional argument is absent and otherwise its value would be use.
  3. A more scalable version is to passing a function which determines the opacity of an object by calling it on a Car object: std::function<double(Car const&)>. The default would be a function which obtains the Car's opacity but it could be some other function, including always return a constant.

Since the third option is a bit more obscure I'll provide an example below (which assumes that the Car has a member function opacity() returning the Car's opacity):

void Car::Draw(std::function<double(Car const&)> getOpacity
                     = std::mem_fn(&Car::opacity)) {
    opacity = getOpacity(*this);
    // ...
}

It is now easy to pass in other function objects somehow copying the opacity:

double ConstantOpacity(Car const&, double value) { return value; }
double ComputeOpacity(Car const& c, double value) { return (c.opacity() + value) / 2; }

Car* car = ...;
car->Draw(std::bind(&Car::opacity, _1));        // use the car's opacity
car->Draw(std::bind(&ConstantOpacity, _1, 0.5); // use opacity 0.5
car->Draw(std::bind(&ComputeOpacity, _1, 0.5);  // average of the car's opacity and 0.5

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.