Why the destructor executes after its friend operator (in which thereis a copy constructor) runs


F

fl

Hi,

I am learning C++ with "C++ Primer" 4th edition. There is a class derivation example in Chapter 15. When I step through the code, I find that the base class destructor
,,,,,,,
~Query() { decr_use(); }
,,,,,,,
executes after the derived class AndQuery() (which is derived from BinaryQuery, which is then derived from Query_base) constructor runs:
,,,,,,,,,,,,,,,,,,
AndQuery(Query left, Query right): BinaryQuery(left, right, "&") { }
,,,,,,,,,,,,,,,,,,

The above problem appears when I run

Query orq = Query(sought1) & Query(sought2);

It triggers the "and" operator. I see that it copies "left" to "lhs" etc.:

BinaryQuery(Query left, Query right, std::string op): lhs(left), rhs right, oper(op) { }

The next call
~Query() { decr_use(); }
puzzles me a lot.

Could you explain it to me?

Thanks in advance
................
// private, abstract class acts as a base class for concrete query types
class Query_base {
friend class Query;
protected:
typedef TextQuery::line_no line_no;
virtual ~Query_base() { }
private:
// eval returns the |set| of lines that this Query matches
virtual std::set<line_no>
eval(const TextQuery&) const = 0;
// display prints the query
virtual std::eek:stream&
display(std::eek:stream& = std::cout) const = 0;
};


// handle class to manage the Query_base inheritance hierarchy
class Query {
// these operators need access to the Query_base* constructor
friend Query operator~(const Query &);
friend Query operator|(const Query&, const Query&);
friend Query operator&(const Query&, const Query&);
public:
Query(const std::string&); // builds a new WordQuery

// copy control to manage pointers and use counting
Query(const Query &c): q(c.q), use(c.use) { ++*use; }
~Query() { decr_use(); }
Query& operator=(const Query&);

// interface functions: will call corresponding Query_base operations
std::set<TextQuery::line_no>
eval(const TextQuery &t) const { return q->eval(t); }
std::eek:stream &display(std::eek:stream &os) const
{ return q->display(os); }
private:
Query(Query_base *query): q(query),
use(new std::size_t(1)) { }
Query_base *q;
std::size_t *use;
void decr_use()
{ if (--*use == 0) { delete q; delete use; } }
};

inline Query& Query::eek:perator=(const Query &rhs)
{
++*rhs.use;
decr_use();
q = rhs.q;
use = rhs.use;
return *this;
}

inline std::eek:stream&
operator<<(std::eek:stream &os, const Query &q)
{
return q.display(os);
}

class WordQuery: public Query_base {
friend class Query; // Query uses the WordQuery constructor
WordQuery(const std::string &s): query_word(s) { }

// concrete class: WordQuery defines all inherited pure virtual functions
std::set<line_no> eval(const TextQuery &t) const
{ return t.run_query(query_word); }
std::eek:stream& display (std::eek:stream &os) const
{ return os << query_word; }
std::string query_word; // word for which to search
};

inline
Query::Query(const std::string &s): q(new WordQuery(s)),
use(new std::size_t(1)) { }

class NotQuery: public Query_base {
friend Query operator~(const Query &);
NotQuery(Query q): query(q) { }

// concrete class: NotQuery defines all inherited pure virtual functions
std::set<line_no> eval(const TextQuery&) const;
std::eek:stream& display(std::eek:stream &os) const
{ return os << "~(" << query << ")"; }
const Query query;
};

class BinaryQuery: public Query_base {
protected:
BinaryQuery(Query left, Query right, std::string op):
lhs(left),
rhs(right),
oper(op)
{ }

// abstract class: BinaryQuery doesn't define eval
std::eek:stream& display(std::eek:stream &os) const
{ return os << "(" << lhs << " " << oper << " "
<< rhs << ")"; }

const Query lhs, rhs; // right- and left-hand operands
const std::string oper; // name of the operator
};

class AndQuery: public BinaryQuery {
friend Query operator&(const Query&, const Query&);
AndQuery(Query left, Query right):
BinaryQuery(left, right, "&") { }

// concrete class: AndQuery inherits display and defines remaining pure virtual
std::set<line_no> eval(const TextQuery&) const;
};

class OrQuery: public BinaryQuery {
friend Query operator|(const Query&, const Query&);
OrQuery(Query left, Query right):
BinaryQuery(left, right, "|") { }

// concrete class: OrQuery inherits display and defines remaining pure virtual
std::set<line_no> eval(const TextQuery&) const;
};

inline Query operator&(const Query &lhs, const Query &rhs)
{
return new AndQuery(lhs, rhs);
}
 
Ad

Advertisements

R

Rui Maciel

fl said:
Hi,

I am learning C++ with "C++ Primer" 4th edition. There is a class
derivation example in Chapter 15. When I step through the code, I find
that the base class destructor ,,,,,,, ~Query() { decr_use(); }
,,,,,,,
executes after the derived class AndQuery() (which is derived from
BinaryQuery, which is then derived from Query_base) constructor runs:
,,,,,,,,,,,,,,,,,, AndQuery(Query left, Query right): BinaryQuery(left,
right, "&") { } ,,,,,,,,,,,,,,,,,,

The above problem appears when I run

Query orq = Query(sought1) & Query(sought2);

It triggers the "and" operator. I see that it copies "left" to "lhs" etc.:

BinaryQuery(Query left, Query right, std::string op): lhs(left), rhs
right, oper(op) { }

The next call
~Query() { decr_use(); }
puzzles me a lot.

Could you explain it to me?

Thanks in advance


First of all, you could help others help you by supplying a small example
which can be compiled and executed. You've mentioned that you suspect that
the problem you've experienced is caused by a statement which is nowhere to
be found in your example. This means that if you expect others to help you,
you are also expecting that they both guess what the code is and spend their
time trying to reproduce it.

Regarding your post, you've referred to the following line:
Query orq = Query(sought1) & Query(sought2);

You've also stated that you aren't sure why a destructor is called.

If you look at the above line, you will notice that it is equivalent to:

Query orq = AndQuery( Query(sought1), Query(sought2) );

In the above statement, you've declared two temporary objects of class
Query, which you pass as parameters to AndQuery(). According to the C++
standard, those temporary objects are destroyed as the last step in
evaluating the full-expression. This means that once that call to
AndQuery() is evaluated, Query::~Query() will be called.

In short, every object has a lifetime. When that lifetime starts the
constructor is called, and when that lifetime ends then the destructor is
called.


Hope this helps,
Rui Maciel
 
F

fl

fl wrote:
















First of all, you could help others help you by supplying a small example

which can be compiled and executed. You've mentioned that you suspect that

the problem you've experienced is caused by a statement which is nowhere to

be found in your example. This means that if you expect others to help you,

you are also expecting that they both guess what the code is and spend their

time trying to reproduce it.



Regarding your post, you've referred to the following line:






You've also stated that you aren't sure why a destructor is called.



If you look at the above line, you will notice that it is equivalent to:



Query orq = AndQuery( Query(sought1), Query(sought2) );



In the above statement, you've declared two temporary objects of class

Query, which you pass as parameters to AndQuery(). According to the C++

standard, those temporary objects are destroyed as the last step in

evaluating the full-expression. This means that once that call to

AndQuery() is evaluated, Query::~Query() will be called.



In short, every object has a lifetime. When that lifetime starts the

constructor is called, and when that lifetime ends then the destructor is

called.





Hope this helps,

Rui Maciel

Thanks for your answer. I do like to post the whole project files along with the question. The problem is that there are 7 small files in this project.. And there is either no zip file upload functionality in this group.

I have a further question here. I am using Microsoft Visual C Express Studio to debug this project. When it runs through ~Query(), I do not know whichparameter copy is destroyed. Is there a way or trick to get that information in debugging? i.e. to know the "*this" content?
Thanks.
 
T

Tobias Müller

fl said:
Thanks for your answer. I do like to post the whole project files along
with the question. The problem is that there are 7 small files in this
project. And there is either no zip file upload functionality in this group.

That's not a good idea. Most people here are not willing to look through a
complete project.
Try reducing your code to a (working) minimum, s.t. the problem still
occurs. Optimally, you will find the problem by yourself in the process,
but at least you will learn something.
I have a further question here. I am using Microsoft Visual C Express
Studio to debug this project. When it runs through ~Query(), I do not
know which parameter copy is destroyed. Is there a way or trick to get
that information in debugging? i.e. to know the "*this" content?
Thanks.

There's a debug window called 'Locals' that shows all local variables and
their values (including 'this').
You can also hover over any variable in the source code to see it's value.

That's off topic here however.

Tobi
 
R

Rui Maciel

fl said:
Thanks for your answer. I do like to post the whole project files along
with the question. The problem is that there are 7 small files in this
project. And there is either no zip file upload functionality in this
group.

You don't need to post your project. You only need to post the smallest
example you can come up with that reproduces the same problem.

I have a further question here. I am using Microsoft Visual C Express
Studio to debug this project. When it runs through ~Query(), I do not know
which parameter copy is destroyed. Is there a way or trick to get that
information in debugging? i.e. to know the "*this" content? Thanks.

First of all, all temporary copies will be destroyed. The standard states
that, when a function is called, the initialization and destruction of each
parameter occurs within the context of the calling function.

The tricky bit is to know what temporary objects are created, and their
creation order. I don't know if the standard even specifies the order in
which local objects within a block are destroyed, and I suspect it doesn't.
Therefore, if you really want to make sure you know in which order they are
created and destroyed, I suspect that the only option you have is to
allocate and deallocate them them dynamically, which would be an ugly hack.

Then again, why should this matter?


Rui Maciel
 
Ad

Advertisements

R

Rui Maciel

Paavo said:
Whenever objects are automatically destructed in C++, this is done in the
reverse order of their creation (more exactly, in the reverse order of the
completion of their construction). This holds even if the creation order
itself is not specified. For temporaries this is covered by [12.2/4] in
the 2003 standard, for example.


That only tells you the relative order in which temporary objects are
destroyed, not the precise order in which that happens. To be able to tell
this, it's necessary to know a bit more stuff, including the order in which
they were created, which in some cases isn't possible to tell.

The standard states that lifetime of an object starts after its
initialization is complete [3.8]. In this example, when a function is
called, the standard states that the sequence in which the parameters are
initialized is indetermined [5.2.2]. Therefore, if it isn't possible to
tell the order in which objects start their lifetime, it isn't possible to
tell which order their lifetime ends.

The only guarantee that the standard gives is that, no matter in which order
they are created, they are destroyed in the reverse order. But that info
isn't enough to let anyone say precisely which object is destroyed first.


Rui Maciel
 
Ad

Advertisements


Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top