Reputation: 93
Let's say I have a class where I have multiple functions with similar logic. Since I don't want to repeat myself, I extract the similar logic in a function. Is it good practice to have similarLogic as a non-member function if it does not use any class members? Or is there a better way to do this?
Please note that in my case, the similarLogic function is strictly specific to MyClass so it won't be used anywhere outside of it.
Example with a non-member function:
MyClass.h
class MyClass {
public:
int func1();
int func2();
};
MyClass.cpp
int similarLogic(int p_num){
return 5 + p_num;
}
int MyClass::func1() {
return similarLogic(1);
}
int MyClass::func2() {
return similarLogic(2);
}
Upvotes: 4
Views: 873
Reputation: 8249
Usually, a method on a class is there to change the state of the instance, whereas a (free) function is hopefully free of side effects. (I'm not saying is has to be, but it should be.) In this spirit, there is absolutely nothing wrong with putting repeated code in a free function somewhere, even more so if it's stateless and free of side effects.
There is another layer your question can be answered on, however. The moment your class is compiled, its layout forms a binary interface that every code using your class is linked against. The moment you change your class by modifying member order, introducing variables or adding methods, you will introduce breaking changes to every caller. In the best case, it's only dependent code that is recompiled, but if your class is exposed through a header file and linked against through a shared or static object, you're up for trouble - regardless of whether your addition was private
or not.
To remedy this, the "PIMPL" idiom can be used, which is described e.g. on cpppatterns.com/patterns/pimpl.html. In essence, the idea is the following:
Instead of having private method or member declarations in your class definition, you only provide public methods and a single pointer to an anonymous class. This "anonymous" class is only defined in the .cpp file backing your class declaration. It's not visible to the outside world, and everything private to your logic is implemented there.
To borrow from cpppatterns, this is how it looks like:
In the header file, you're defining your class foo
only with members that are publicly accessible and a single private pointer pimpl
of an anonymously declared type foo::impl
.
// foo.h - header file
#include <memory>
class foo
{
public:
foo();
~foo();
foo(foo&&);
foo& operator=(foo&&);
private:
class impl;
std::unique_ptr<impl> pimpl;
};
In the code, you're both defining the anonyous class foo::impl
, and make every public method of foo
call out to the private implementation in foo::impl
. It's this foo::impl
class that's carrying your actual state:
// foo.cpp - implementation file
class foo::impl
{
public:
void do_internal_work()
{
internal_data = 5;
}
private:
int internal_data = 0;
};
foo::foo()
: pimpl{std::make_unique<impl>()}
{
pimpl->do_internal_work();
}
foo::~foo() = default;
foo::foo(foo&&) = default;
foo& foo::operator=(foo&&) = default;
If you try this, note that the last three lines here are key to make it work, as the constructor, destructor and copy operator can only be defined when the type foo::impl
is known.
Very much like your idea with the free, "private" functions, no changes introduced to the core logic in foo::impl
will ever be observable by any user of foo
, and you get to have class methods operating on state.
The cost, on the other hand, is that of introducing more code to write and maintain, and that of (at least theoretically) passing the calls along the private pointer to the actual implementation.
So ... pick the right tool for the job. The one you thought of is fine. :)
Upvotes: 3
Reputation: 2070
If the functionality is only used by MyClass
then the function should be a private member function.
If it does not use any MyClass
attributes you can eventually make it static, but it's not necessary.
If it's similar logic that can be applied to different type, you can also use a template function. For instance :
// MyClass.h
class MyClass {
public:
int func1();
int func2();
private:
template<class T> void similarLogic(T arg);
};
template<class T> MyClass::similarLogic(T arg) {
// Generic implementation
}
template<> MyClass::similarLogic<float >(float arg) {
// Specialization for int
}
// MyClass.cpp
int MyClass::func1() {
// Call generic implementation for int
return similarLogic<int>(1);
}
int MyClass::func2() {
// Call specialization for float
return similarLogic<float>(2.34);
}
Upvotes: 2
Reputation: 234695
Personally, I'd write similarLogic
in an anonymous namespace within MyClass.cpp
. In that way it's invisible outside that translation unit:
namespace {
int similarLogic(int p_num){
return 5 + p_num;
}
}
Other approaches such as a private
member of the class (static
or otherwise) pollute the class definition. But that doesn't make the approach a poor choice. If the function requires class member data in the future, then refactoring is simpler than it would be with my favoured way.
Whatever you end up doing, polluting the global namespace is not desirable.
Upvotes: 9