Question:
This is a small wrapper for a blogging system; the library used is in C, so I have simply added an interface to std::stream
:
#include <sstream>
#include <iostream>
class ILogger;
class Stream {
friend class ::ILogger;
std::stringstream m_ss;
Stream( const Stream & );
Stream &operator=( const Stream & );
public:
~Stream( ) {
std::cout << m_ss.str( ) << std::endl;
}
inline Stream( ) { }
template< typename T > inline Stream &operator<<( const T &val ) {
m_ss << val;
return *this;
}
};
class ILogger {
public:
template< typename T > inline Stream operator<<( const T &val ) {
Stream s;
s << val;
return s;
}
};
int main( ) {
ILogger log;
log << "Hola" << " mundo " << "cruel";
return 0;
}
The library uses printf
– like functions, so we just output a string via a std::stringstream
via a helper class, and destroying the instance of that helper is when the actual C function is called.
To my surprise, the code works!
I say this because I was expecting a compile error, having Stream::Stream( const Stream & )
declared as private
. I expected that when going out of scope, the copy constructor and destructor would be called. I haven't said it yet, but this is for C++98.
template< typename T > inline Stream operator<<( const T &val ) {
Stream s;
s << val;
return s; // salimos del ámbito. 's' se pierde
}
I'm compiling with g++ 7.2
, with the command g++ -std=c++98 -Wall -Wextra -pedantic
.
- The question is: Is this the expected behavior, or is it a feature of my compiler? Can I trust that I get the same results on older compilers ?
Note : The purpose of that code is to run on very old machines, with SDK no longer supported… and I don't want any last minute surprises :-O
Answer:
Where is the surprise? that's what Friends Are For!
class Stream {
friend class ::ILogger;
/* codigo */
}
By having Stream
as a friend ILogger
will have access even to the private sections of Stream
, both variables and functions, including special functions.
Is this the expected behavior, or is it a feature of my compiler?
It is not a compiler feature, it is an intrinsic feature of the language, it is indicated by the standard in section 14.3 (my translation):
14.3 Friends [class.friend]
- A class's friend is a function or class that is granted permissions to use the private and protected members of that class. A class specifies its friends, if any, using friend declarations. These declarations give wizards special access rights, but do not make friends members of the classes they are friends with.
Can I trust that I get the same results on older compilers ?
Yes. The following code behaves like yours:
class C
{
friend struct X;
void privada() { std::cout << __PRETTY_FUNCTION__ << '\n'; }
public:
void publica() { std::cout << __PRETTY_FUNCTION__ << '\n'; }
};
struct X
{
/* X puede usar C::privada, porque C se lo permite
con su amistad. */
void f() { C c; c.privada(); }
};
And this fails:
struct Y
{
/* Error de compilación, Y no puede usar C::privada
porque C no le otorgó amistad. */
void f() { C c; c.privada(); }
};
I expected that when going out of scope, the copy constructor and destructor would be called.
This is so in some cases, and in others the compiler applies an optimization known as "Return Value Optimization" (RVO) 1 , this optimization consists in the fact that the compiler can decide to build an object outside the function that creates it if it detects that doing this does not cause any side effects, thus skipping a copy. This optimization has been built into the language almost from the beginning, so in C++98 it is available.
You may get the behavior you expect if you disable all optimizations in your compiler.
- Known in English as "Return Value Optimization" RVO.