I generally use strong-types-for-strong-interfaces approach to prevent wrong ordering and also state the intention more clearly. It could be found a bit verbose but it makes the code more readable and less error-prone when used appropriately. You can read the article for further and detailed explanation.
I am just sharing a simple demonstration how to represent the order and meaning of the constructor parameters explicitly for the Sprite class.
#include <string>
template <typename T, typename param>
class strong_type
{
public:
explicit strong_type ( T const& value ) : m_value ( value ) {}
explicit strong_type ( T&& value = T{} ) : m_value ( std::move ( value ) ) {}
T& get()
{
return m_value;
}
T const& get() const
{
return m_value;
}
private:
T m_value;
};
using animation_speed = strong_type<int , struct AnimationSpeedTag>;
using frame_width = strong_type<int , struct FrameWidthTag>;
using frame_height = strong_type<int , struct FrameHeightTag>;
using width = strong_type<int , struct WidthTag>;
using height = strong_type<int , struct HeightTag>;
using scale = strong_type<int , struct ScaleTag>;
class Sprite
{
public:
Sprite( const std::string &texture_file , animation_speed s , frame_width fw ,
frame_height fh , width w , height h , scale sc )
: m_texture_file { texture_file }
, m_animation_speed { s.get() }
, m_frame_width { fw.get() }
, m_frame_height { fh.get() }
, m_width { w.get() }
, m_height { h.get() }
, m_scale { sc.get() }
{ }
private:
// There is no harm to hold the values as primitive types
// Just take it as `strong type`
std::string m_texture_file;
int m_animation_speed {};
int m_frame_width {};
int m_frame_height {};
int m_width {};
int m_height {};
int m_scale {};
};
int main() {
// instead of
// Sprite my_sprite { "sprites/char.dds" , 12 , 640 , 480 , 320 , 240 , 1 };
Sprite my_sprite { "sprites/char.dds" , animation_speed { 12 } ,
frame_width { 640 } , frame_height { 480 } ,
width { 320 } , height { 240 } , scale { 1 } };
}
run online
Another way is passing a struct which holds the required parameters, but I don't suggest that way. Because actually it is not solving the problem, just moving it one step further. The parameters in struct still might have been left unassigned.
A demonstration :
#include <string>
struct sprite_parameters
{
std::string texture_file;
int animation_speed {};
int frame_width {};
int frame_height {};
int width {};
int height {};
int scale {};
};
class Sprite
{
public:
Sprite( const sprite_parameters& params )
: m_texture_file { params.texture_file }
, m_animation_speed { params.animation_speed }
, m_frame_width { params.frame_width }
, m_frame_height { params.frame_height }
, m_width { params.width }
, m_height { params.height }
, m_scale { params.scale }
{ }
private:
std::string m_texture_file;
int m_animation_speed {};
int m_frame_width {};
int m_frame_height {};
int m_width {};
int m_height {};
int m_scale {};
};
int main() {
// instead of
// Sprite my_sprite { "sprites/char.dds" , 12 , 640 , 480 , 320 , 240 , 1 };
sprite_parameters params;
params.texture_file = "sprites/char.dds";
params.animation_speed = 150;
params.frame_width = 320;
params.frame_height = 240;
params.width = 640;
params.height = 480;
// params.scale = 1; OK, I forgot this, what will happen now.
// It is just ugly obscure bug.
Sprite my_sprite { params };
}
run online