This piece of code is a synthetic example extracted from a real world app.
This code doesn't make much sense, its only purpose is illustration and everything that is not relevant has been stripped.
#include <iostream>
class st
{
public:
int m = 1;
st() {
std::cout << "st()\n";
};
st(const st *source) {
m = source->m;
std::cout << "st(const st *source) " << m << "\n";
};
};
void Foo(const st& bar) {
std::cout << "Foo" << "\n";
}
int main() {
st foo;
const st* p = &foo;
Foo(p); //(1)
}
Output
st()
st(const st *source) 1
Foo
The problem here is when I call the void Foo(const st& bar) function with a pointer to st as in the line with comment //(1) following happens:
As there is no Foo function that takes a st* as argument, a temporary object is created via the st(const st *) constructor and then a reference to this temporary object is passed to Foo. The code works as expected, but it took me a while to figure out why the st constructor was called.
I just wonder how this could have been prevented. I suppose either:
void Foo(const st& bar)should rather bevoid Foo(const st *bar)- or there shouldn't be a
st(const st *source)constructor but rather ast(const st & source)constructor. - or are there some C++ attributes that may generate warnings in such cases?
I'm using the C++20 standard.
2 Answers 2
Implicit conversion from pointer to object is indeed recipe for surprise. It's too simple to make a typo and get code that silently does the wrong thing.
It is generally recommended to declare constructors that participate in conversions as explicit (see eg cppcoreguidelines) to avoid implicit conversions:
explicit st(const st *source) {
m = source->m;
cout << "st(const st *source) " << m << "\n";
};
Then Foo(p); is an error while you had to write Foo(st{p}); for the explicit conversion.
Alternatively, you can consider to use a named method rather than a constructor for the conversion:
static st from_pointer(const st *source) {
// ...
}
Depends on why you need the conversion and how it is used. If you are looking for a way to keep the conversion implicit but at the same time prevent Foo(p) from compiling, I don't think this can be done.
Addressing your list of alternatives:
void Foo(const st& bar)should rather bevoid Foo(const st *bar)
I cannot tell. Does Foo allow nullptr or otherwise invalid pointers to be passed? If no, use a reference.
or there shouldn't be a
st(const st *source)constructor but rather ast(const st & source)constructor.
That would be a copy constructor. If it makes sense for st to define a copy constructor, go for it. All other code that wants to copy a st will use that same copy constructor. If that helps to remove the converting constructor even better.
or are there some C++ attributes that may generate warnings in such cases?
explicit is not new and it generates an error. I am not aware of (new) attributes that generate a warning.
9 Comments
explicit, which I suppose makes sense.explicit is useful (perhaps a little bit less than for 1 argument constructors, but only just a little) godbolt.org/z/zKWYz7nrn.Unfortunately, this answer missed the main problem. The OP needs a copy constructor:
st::st(st const& src)
: m{src.m} //constructor init list
{ std::println("{:s}", std::source_locactuion::current().function()); };
Note the use of constructor initializer list. It's important for classes with expensive resources or const members.
The other notice is the rule 3/5/0. I will not go deep into that, but since you have a copy constructor, you will need an assignment operator to match, and a destructor:
auto& st::operator=(st const& src){
m = src.m;
std::println("{:s}", std::source_location::current().function()); };
}; //! st=
st::~st(){
//release all resources
std::println("{:s}", std::source_location::current().function()); };
}; //! ~st()
Not following the rule 3/5/0 is going to cost severe resource bugs (use after free, double free, leak) in future.
The constructor in original snippet is never used in C++ programming. In corner cases where it might make sense, a tag can be used to make it verbose:
struct st_tag_t{};
constexpr st_tage_t st_tag;
explicit st::st(st_tag_t, st const*);
This eliminates unintended use of this constructor in all scenarios.
6 Comments
st(st *source)) and ignores all of your additions. If you have a specific fix here, I think you need to provide additional compilable code that demonstrates how your suggestion applies to OP.Explore related questions
See similar questions with these tags.
st(const st *source)should handle thesource == nullptrsituation. Either by throwing, or behaving likest()constructor.