I'm trying to create a smart pointer and stumbled over the code below. Since I'm pretty new to C , its syntax is yet something I have to get used to.
Below you can see the most important part of the code where RC
is the reference counter class with a member variable called count
(of type int
). The function addRef
increments count
with 1.
template < typename T > class SP
{
private:
T* pData; // pointer
RC* reference; // Reference count
public:
SP() : pData(0), reference(0)
{
reference = new RC();
reference->AddRef(); // Increment the reference count
}
//...
}
Now, what exactly happens when the constructor assigns 0
to pData
and reference
?
Further, is there a reason why a "default parameter" was omitted here? Something like this:
SP() : pData(0), reference(new RC())
CodePudding user response:
It will be initialized to 0 before the constructor code runs inside a constructor preamble. And then get overridden to another value when you assign it inside the constructor.
You're right - you can just do reference(new RC()) inside the constructor member initializer list. It is probably just stylistic that the original developer did it this way, or maybe they want to have the opportunity to put a breakpoint there. Or they could have worked on an older compiler that didn't support more complex initializers.
Setting to 0 and immediately overwriting it without reading the value will most likely be thrown out by the compiler optimizer, so it's not really a performance penalty.
PS: It's not a default parameter. Default parameters are filled in by the caller, this gets run by the callee.
CodePudding user response:
For historical reasons, 0
is equivalent to nullptr
, which means "a pointer that does not point to anything". In modern code, you should always use nullptr
, never 0
.
Is this the way to go or are there "better" best practices?
Putting aside concerns about what is being executed, the most idiomatic way to write the same functionality as presented in the question is:
- Use
nullptr
to express that a pointer points to nothing. - Use member initializers for default member values.
- Use initializer lists to specify member initial values in place of the default when it differs.
- Bonus: Always match your
new
s withdelete
s, even in sample code.
template < typename T > class SP
{
private:
T* pData = nullptr;
RC* reference = nullptr;
public:
SP() : reference(new RC())
{
reference->AddRef();
}
SP(const SP&) = delete;
SP& operator=(const SP&) = delete;
~SP() {
delete reference;
}
//...
}