C++ Quiz (July 2002)

small number near big Q shows the difficulty of the question

Q110

Alice is curious. If a is an object of class A, then we can call member functions of A for a, even destructor: a.~A(). Can we call a constructor of A for a ?

A1

Thank you, everybody who has answered the question. It turned out to be a hard and tricky question as I received very many 'no' answers. Unfortunately there are no good answer which can be regarded as correct. Please we are welcome to discuss it on "masc.technical".

Of course, the constructor cannot be called directly. But anyway the answer to the question is Yes.

Usually it is not much point to call a constructor for an already created object. I cannot find the reason to call the constructor a second time. But it can be called after calling the destructor, like

{
	A a; // a is born
	... // some work
	a.~A(); // kill a
	...// now the soul of a is dead
	...// but the body still exists
	a.A() // (illegal) reincarnation
	...// now a is alive again
} // elimination of a

While a.A() call is illegal one can do it by using placement new operator:


new(&a) A;  // equvalent to "pseudocode a.A()"
or
new(&a) A(another); // equvalent to "pseudocode a.A(another)"

Operator new does 2 things. 1) allocates space for the future object and then 2) calls a constructor function which takes the address of the allocated space. That is why if allocation fails the constructor is never called even if you redefine new operator - if your new operator returns 0 the call to the constructor function is skipped. In our case the placement new operator skips the allocation and calls the constructor.

The following example though shows how this can be utilized. [This is perfectly legal and well-defined code]

struct A{
	const A& A::operator=(const A& x){
		if( this != &x ){
			this->~A();
			new(this) A(x);
		}
		return *this;
	}
	~A(){...}
};

This definition of operator= uses copy constructor delegating its semantics - whenever copy constructor changes, the behavior of operator= also changes accordingly.
[Note: One interesting point about the example above. The destructor in contrast to other member function cannot be called without explicit operators '->' or '.'.  The expression '~A()' will be treated by the compiler as '~(A())' which is a complementary of a temporary A object.]


Q28

Alice says that her compiler has a bug. It does not produce an error, even a warning as she expected in the following example.

typedef char * ptr;
void f(const ptr p){
 *p = 'a';  // const char is changed
}
Is she right?

A2

The correct answer of Mathew Hounsel is here.

No. The const refers to the entire typedef which is char *. This makes the argument a constant pointer to mutable characters not a pointer to constant characters as she thought.

void f( char const * p){ // What she thought.
 *p = 'a'; // Illegal
 ++p; // Fine
}

void g( char * const p){ // What she got.
 *p = 'a'; // Fine
 ++p; // Illegal
}

I just can add.

After declaration:
typedef A * ptr;
(const ptr) is NOT the same as (const A *) because const qualifier acts on name (ptr) which is synonym of (A*) and not on (A). So (const ptr) actually means (A * const).
This is sometimes confusing, note:

(const A *) == (A const *) != (A * const)

Well, that shows that to typedef real C pointers is never clever idea.


Q312

Alice does not like RTTI: "Why do I have to pay for something I don't use!"
How big is RTTI memory overhead for a given object a of class A ?

RTTI stands for Run Time Type Identification

A3

Zero!

This question can again be a controversial one, because "The structure used for RTTI information and where it is placed is a implementation detail and not mentioned in the ISO C++ standard." (Pratik Khasnabis).

However, RTTI was accepted under assumption that it would not cause memory overhead for objects. The trick is that RTTI is used only for polymorphic classes (classes that declare or inherit a virtual function) which have already vtbl pointer, and vtbl is responsible for holding that information (remember that vtbl is one per class not per object). Again I would like to cite the good and detailed answer of Mathew Hounsel (I do not encourage people to answer in much detail. A short precise answer and a terse brief explanation is enough):

No additional memory is required for each object. A single word is required for each class.

Type Identification (TI) of any expression where the type can be determined at compile time (Static TI) has no execution cost. It will have the storage cost for a single static type identifier (1 word min) for each class where STI is used.

There is no cost for TI unless it is used with polymorphic objects because then it must be done at runtime. Consider inheritance

    class A {}; 
    class B : public A {}; 
Any object of type B can not be used polymorphically with a pointer to type A, as there is no virtual functions.

Bjarne suggested a reference implementation, "The design and Evolution of C++", that declared that RTTI can only really be used on classes with virtual members. He added an additional field at the start of the vtable that contained a pointer to the type identifier, one byte of storage, two indirections for identification.

However, since the virtual table would have to be unique for each class to contain the correct pointer, the address of the vtable would be unique, and could be used as the type identifier. As such, there would be no per object storage overhead.

The real issues is the storage of the type_info::name. The name must only be unique and since the data is implementation dependant it doesn't have to be helpful. It could probably be stored in a single word, with one byte for '\0'. This could be stored in the first entry of the vTable.

So in theory the minimum storage overhead would be one word per class. There is no execution overhead unless you use RTTI, and if you do there is no overhead to get the identifier and one indirection to get the name.

But, I would say to Alice, read the compiler manual and turn of RTTI as most compilers have that option.


Q48

Bjarne says: "... It is a surprise to most experienced C and C++ programmers that &vc2[200] isn't completely equivalent to vc2+200. In fact, it was a surprise to the C committee also and I expect it to be fixed in the upcoming revision of the standard. ..." (Here he is talking about plain C arrays). Question: What actually should be fixed ?

A4

" &vc1[200] equals &(*((vc1)+(200))) equals &*(vc1+200) .
What need to be fixed is that &* should cancel out. " (Fenwick Peng)

Many have been tricked by the fact that this citation relates to the paragraph 2.7.2 of "The C++ Programming Language":

char vc1[200];
char vc2[500];

void f()
{
 copy(&vc1[0],&vc1[200],&vc2[0]);
}
But actually it is not about taking the address of one-past-the-last element of an array.

Bjarne:

The issue is whether taking the address of one-past-the-last element of an array is conforming C and C++. I could make the example clearly conforming by a simple rewrite:

 copy(vc1,vc1+200,vc2);
However, I don't want to introduce addition to pointers at this point of the book. It is a surprise to most experienced C and C++ programmers that &vc1[200] isn't completely equivalent to vc1+200. In fact, it was a surprise to the C committee also and I expect it to be fixed in the upcoming revision of the standard. (also resolved for C9x - bs 10/13/98).
...
It [&*(vc1+200) == vc1+200] is false in C89 and C++, but not in K&R C or C9x. The C89 standard simply said that &*(vc1+200) means dereference vc1+200 (which is an error) and then take the address of the result, and the C++ standard copied the C89 wording. K&R C and C9x say that &* cancels out so that &*(vc1+200) == vc2+200.

A little clarification. Dereference means knowing lvalue of expression (*p). It is not specified whether or not the corresponding rvalue can be obtained by an implementation (compiler) at the point of dereferencing. In practice, of course, we will not calculate rvalue to throw it away.


Q510

Alice is completely lost in the following few lines:

struct B{ 
	virtual void f(int=4);  // function 1
};
struct D:B{
	virtual void f(); // function 2
private:
	void f(int=5); // function 3
};
...
B * p = new D;
(*p).f(); // function call
What function (if any) and with what argument (if any) is called? Why that function is called and why that argument is chosen ?

A5

"function 3, param = 4. if the virtual wasn't there it would call the base function. ... The base pointer is used and so all the default parameter and public/private information comes from the base class ..." (Michael Farr)

The call is polymorphic. Though the dot operator can be confusing. Expressions p->f() and (*p).f() are exactly the same. Even

D d;
B &r = d; 
r.f();   // is a polymorphic call
Attributes such as default arguments and access control are checked statically (at the stage of compiling) so, during run time, access control and default arguments do not make sense. Static check allows a call of a public B::f; compiler puts the default argument 4, and produces the code for the polymorphic resolution.


Q68

Alice has written:

1:	struct B { 
2:	   virtual void f(){}
3:	};
4:	struct D : B { 
5:	   char a;
6:	   void f(){}
7:	};
. . . 
8:	B * p = new D[2];
9:	p[1].f();
But she is not sure whether it is legal and correct.
Can this code modify unrelated data in memory?

A6

Yes it can. Worse, it ends up in crash. Lines 8 and 9 legal but not correct. Classes B and D have different sizes, and p[1] does not make sense.

Assume, that our implementation puts vtbl pointer in front of other members. First, object D is aligned, so member a is a part of bigger chunk, say A. The memory where p is pointing to consists of { {vtbl, A}, {vtbl, A} }. But the type of pointer p says that the memory should be { {vtbl}, {vtbl}, . . . }. Hence the virtual call p[1].f() is done through A treated as vtbl, which is rubbish. So we jump to nowhere. It is improbable that process will continue on, but in theory it can "modify unrelated data in memory".

Note 1. We can copy vtbl to A. For example, this will not crash (implementation dependable)

8:	B * p = new D[2];
	*(1+(void**)p ) = *(0+(void**)p ); 
9:	p[1].f(); // now p[1].f() calls correct function (with wrong object)

Note 2. The line 8 would be fine only if the derived class D does not add any data members (and, of course, if there is no multiple inheritance). But it is no good anyway.

Note 3. Destruction of such beast will be undefined according to standard, if the user provides delete[] operators for classes B and D .


Q714

What function (if any) will be called in this example? Why?

template <class T> void f(T);  // f1
template <class T> void f(T*); // f2
template <> void f <int*> (int*); // f3
. . .
 int * p = 0;
 f(p);

A7

Surprise, f2 is called.
One common mistake is that f3 is the most specialized template. f3 is a specialization of f1. There is no partial specialization for function templates; f1 and f2 two different template functions. And for f(p), f2 is a better template than f1.

Let us look at this:

template <class T> void f(T);  // f1
. . .
int * q;
f(q); // makes specialization of f1 , the same as 
      // template <> void f<int*>(int*); 
. . .
template <class T> void f(T*); // f2
. . .
 int * p = 0;
 f(p);
We have 2 templates f1 and f2 and one specialization (line: f(q);) of f1. If we would comment out the line f(q); it should not change the meaning of the line f(p); should it? The better template is f2, so it is chosen.

If we would have

template <> void f<int>(int*); // f3 (note no *)
or
template <> void f(int*); // f3 after declaration of f2
then this specialization would be of f2, and in this case f3 would be chosen.
Tricky.


Q812

Working on optimization Alice thought she could avoid an object creation in the function f1, and she replaced function f1 by f2.

struct A { A(); ~A(); };
void cA();
void dA();
void g();

void f1(){ A a; g(); }
void f2(){ cA(); g(); dA(); }
. . .
// somewhere else in another file
A::A(){}
void cA(){} // body is the same as A::A
A::~A(){}
void dA(){} // body is the same as A::~A
void g(){}
Do functions f1 and f2 take different time to execute?
[Hint: What is the purpose of the function g() in the question?]

A8

Yes, because functions f1 and f2 are different.

Object creation in function f1 is more then just calls to constructor and destructor function. Why? see item 3 below.

Alice could find 4 differences: 2 small, 1 important, and 1 negligible.

If the function g() would be declared as
void g() throw(); 
then (neglecting points 1, 2, and 4) we could say that f1 and f2 are equivalent. However a compiler might again not take enough care about the body of the function f1. It can produce the exception handling code even if the body of the function is empty (that would be a bad compiler then).


Q912

Alice says that while a static member function cannot be virtual, she knows one exception to this rule, when a static function undergo a polymorphic resolution. What does she have in mind?
[Hint: What static functions cannot be non-static?]

A9

She has in mind a static deallocation function.

Let us look at the following example

struct B{
	virtual ~B();
	void * operator new(size_t);
	void operator delete(void*);
};

struct D : B {
	~D();
	void * operator new(size_t);
	void operator delete(void*);
}
. . .
B * p = new D;
delete p;
We know that the proper destructor ~D() is called for the object of D pointed by p. [Do not forget that ~B() is also called within ~D() for the B sub-object.] But which operator delete should be chosen, of B or of D? What if memory allocation and deallocation functions work differently for B and D objects?

If B::operator delete would be chosen that would be very wrong, because the object was created in the memory allocated by D::new, not B::new.

Conclusion: deallocator is chosen like a destructor in the class which object really is of. Since a deallocation function is static it cannot be virtual. But in case (only) when the destructor is virtual, the call to this function is resolved dynamically.

Note, back to the question 5, a static check for the deallocation function call is done relying on the base class. So B::operator delete must be accessible even if D::operator delete is actually called.


Q1010

Alice thinks she can cheat const restrictions. She presents the following example:

1:	const int c = 1;
2:	int * p;
3:	const int ** q = &p; // q points to p
4:	*q = &c;             // now p points to c
5:	*p = 10;             // modify c !
Is she wrong? Why does the language work that way?

A10

Alice mistakes at the line 3.
Conversion [int** -> const int**] is illegal. If this does not surprise you, then look at this

int p1;
const int * q1 = &p1;
This is legal (conversion [int* -> const int*]), because we increase constness - we cannot modify the object via pointer q1 regardless the object is const or not.

So what is wrong with [int** -> const int**] ? Here we supposedly increase constness as well. Yes, but doing this we also introduce an intermediate hole - a pointer p between our pointer q and the object c. Now we do not have all the constness control on the object. To cover that hole we have to declare the intermediate pointer as const: [int** -> const int * const *]. Then the line 4 of Alice's example will not work, because (*q) is a const variable and cannot be modified. Another way is to declare p as 'const int *', but then again line 5 will be an error. There is no way to deceive const restrictions (without explicit conversions). As we see constness is a very powerful thing.

Below are examples of legal conversions:

int* -> const int*
int** -> int * const *
int** -> const int * const *
int*** -> int ** const *
int*** -> int * const * const *
int*** -> const int * const * const *
etc
And those are illegal:
int** -> const int **
int*** -> const int ***
int*** -> int * const **
int*** -> const int * const **
etc
Looks awkward, but there is a simple rule: if you introduce const, all the intermediate pointers (from the pointer or object you make const till the pointer which your pointer is pointing to) should be const.

PS Now I am not sure if this answer clarifies the subject or makes it even more obscure.


Quiz discussions


Q1

Date: Tue, 09 Jul 2002 13:35:46 +0930
From: Pretik Krykiferh <pkhasnab@udr.zhop.brr.con>

I disagree with the answer (
http://www.udr.zhop.brr.con/~omikusha/question.html)

from your explanation

While a.A() call is illegal one can do it by using placement new operator:

new(&a) A;  // equvalent to "pseudocode a.A()"
or
new(&a) A(another); // equvalent to "pseudocode a.A(another)"

Not the comment equivalent to ( but not equal to).

We can call placement new operator which implicitly call the constructor ,
but
this
is not the same as directly calling ctor.

Pretik

Date: Tue, 09 Jul 2002 13:46:36 +0930
From: Oleg Mikusha <maz@milevena.com>

The question was not about a direct call to constructor.
It was, in general, "Can we call a constructor of A for a ?"
or not.

 - Oleg


Date: Tue, 09 Jul 2002 13:49:43 +0930
From: Pretik Krykiferh <pkhasnab@udr.zhop.brr.con>

Hmmm
I assumed call means direct call !!

Pretik


Date: Tue, 09 Jul 2002 15:34:15 +0930
From: Michael Zhal <mZhal@mailhost.asc>

Oleg Mikusha wrote:

> The question was not about a direct call to constructor.
> It was, in general, "Can we call a constructor of A for a ?"
> or not.

hrm I suppose if you had asked can you call the constructor for a in place
the answer would have been self evident, but I agree with Pretik ... I didn'
think that was what you were asking.  There is a bunch of calls that happen
before the actual call to the constructor is made ... I thought you were
asking if it is possible to make the direct call ... which is possible but
difficult (as opposed to using a normal language construct).

Seriously though, great questions keep em coming ;-)


Date: Tue, 09 Jul 2002 15:47:47 +0930
From: Oleg Mikusha <maz@milevena.com>

The placement new operator is a direct call to the constructor.
It's just has a different syntax.
That is why calling explicitly the destructor and then
the constructor(using placement new syntax) is well defined.


Date: Tue, 09 Jul 2002 16:16:22 +0930
From: Michael Zhal <mZhal@mailhost.asc>

In terms of indirect I meant that it calls the new function first.  There is
no way to separate the constructor call from the new function.  in other
words I couldn't do
 (&a) A;
but I could do
new(&a) A;

Assuming there is a function like the following it will go in here before
the constructor.

  void* operator new( unsigned bog, void* log)
  {
    return log;
  }

hence the indirect thing.

But yeah I understand that your answer answers you question.  I just didn't
think of it like that when I read the question (I should have).


Date: Tue, 09 Jul 2002 16:23:34 +0930
From: Oleg Mikusha <maz@milevena.com>

Yes. Agreed.

Date: Fri, 12 Jul 2002 15:14:36 +1000
From: Mulray Hytoppee <mhounsel@syd.udr.zhop.brr.con>

> The following example though shows how this can be utilized. [This is
perfectly
legal and well-defined code]
>
> struct A{
>         const A& A::operator=(const A& x){
>                 if( this != &x ){
>                        this->~A();
>                        new(this) A(x);
>                }
>                return *this;
>        }
>        ~A(){...}
>};

This code is legal but it is not well defined. "Exceptional C++" (2000),
Herb
Sutter, Addison Wesley, Reading USA, 0-201-61562-2"
>From "Item 41: Object Lifetimes":
    "This idiom is frequently recommended, and it appears as an example in
the C++
standard ... It is also exceedingly poor form and causes no end of problems.
Don't
do it."
    "This idiom is expressing copy assignment in terms of copy
construction." Better
code follows.

    class B : public A {
        // more members
        const B & operator=( const B & x ) {
            A::operator=( x );
            // Other assignments.
            return *this;
        }
    };

    "Problem 1: It Can Slice Objects."
        If A is a base class then 'this->~A();' calls the derived
destructor.
'new(this) A(x)' creates an A and only an A in the memory.
        The derived class can not constructed properly only an A was. Worse
the
members of B were destroyed and you are assigning to destroyed objects and
that is
undefined at best.
        All derived classes must use this idiom, any implict operators will
be
invalid.

    "Problem 2: It's Not Exception Safe"
        After the destruction any throwing code may leave the object in an
undefined
state. Worse the caller has no idea that the object has been destroyed and
may
attempt to destroy it again when it catches the exception, double
destruction is
bad.

    "Problem 3: It Changes Normal Lifetimes"
        It causes problems if constructors and destructors have side
effects. If the
derived class adds a mutex and then locks the class on entry to each method,
the
destructor would try and release the mutex and would fail. This prevents
design
where construction is aquisition.

    "Problem 4: It Can Still Break Derived Classes"
        The base class destructor is dangerous any derived class might not
be
expecting the destruction, especially if it handles the base classes state.
Such as:

        const B & B::operator=( const B & x ) {
            // Modify a base classes member.
            A::operator=( x );
            // Other assignments.
            return *this;
        }

    "Problem 5: this != &x"
        Without this test it is lethal. Operators that test this are never
exception
safe and with overloads are down right dangerous.

>From "Item 13: Writing Exception Safe Code":
    "Elegant Copy Assignment"
        This requires an implementatition of Swap function that does not
throw.
"void Swap( T & A ) throw ();"
        A & A::operator=( const A & x ) {
            A t( x );
            Swap( t );
            return *this;
        };
        A temporary copy is created. The contents of the copy and the
destination
are swapped. The old contents are destroyed when the temporary is destroyed.
        If Swap is exception safe this is exception safe. There is no
duplicated
code. And if Swap is virtual then the function is polymorphic.

Date: Fri, 12 Jul 2002 15:33:01 +0930
From: Pretik Krykiferh <pkhasnab@udr.zhop.brr.con>

Hi Murlray ,
I've read Herb's book also , but the example as presented by Oleg may be
well defined
if you assume struct A as only a stand alone struct not involved in any
inheritance as
base
or derived class. This assumption rules out problem 1 & 4.
If we further assume exception safe destructor which are trivial with no
side effect 2,3
is also gone
Problem 5 is not a problem in the example as the check was done.

Bottomline without knowing the complete declaration/definition of class A
the question
cannot
be answered properly.
Can I request Oleg to provide a minimum compilable program be given in each
question so
that
we don't have to answer anyting with assumptions.

e.g
in Quiz 4
> Bjarne says: "... It is a surprise to most experienced C
> and C++ programmers that &vc2[200] isn't completely equivalent
> to vc2+200. In fact, it was a surprise to the C
> committee also and I expect it to be fixed
> in the upcoming revision of the standard. ..."
> (Here he is talking about plain C arrays).
> Question: What actually should be fixed ?

Ther is nothing wrong if  array vc2 was declared as
char vc2[400];
but some problem if declared as
char vc2[200];

So can we get more well defined questions please ?

Pretik

Date: Fri, 12 Jul 2002 16:08:57 +0930
From: Oleg Mikusha <maz@milevena.com>

Sorry for misunderstanding.
In that question I wanted to ask about the difference between
&a[i] and a+i, but I did not want to edit the quotation.
(Why the quote in the question and the answer differs is that
they have been taken from different sources).
Also I pick that out of context in hope that it would not
mislead somebody into arrays boundary.

Murlray's letter is very appropriate and constructive.
With C++ you do only what you know you are doing, not to
"blow your whole leg off".

Thank you Mulray.
Thank you Pretik.

 - Oleg

Date: Mon, 15 Jul 2002 10:02:57 +1000
From: Mulray Hytoppee <mhounsel@syd.udr.zhop.brr.con>

"Bottomline without knowing the complete declaration/definition of class A
the question cannot be answered properly."

I wrote this response to warn people of the potential for serious problems.
Fancy code should only be used when you are completely aware of the
consequences.

Date: Mon, 15 Jul 2002 10:14:49 +0930
From: Oleg Mikusha <maz@milevena.com>

> Can I request Oleg to provide a minimum compilable program be given in
each question so
> that
> we don't have to answer anyting with assumptions.
>
> Pretik

If a compilable program would be given in each
question, then it will be easy to feed it to compiler, see result, and
try to
explain it. For me, it would be hard to distinguish explanations of
people who
really understand the problem from of those who do not. And, of course,
all straight direct answers would be correct.

 - Oleg

Date: Mon, 15 Jul 2002 10:33:28 +0930
From: Oleg Mikusha <maz@milevena.com>

Hi Mulray,

I have had a thought of your letter.
I find the problems you have presented to be sound and
explaining why that technique should be avoided.
But I still disagree with you saying that my example is
not well defined. Unfortunately you did not provide a
counterexample which shows that my class  produce an
undefined behaviour. So I will go through each of your points.

My only assumption would be that ~A(){...} does not throw,
which is obvious I suppose. [Note, that I do not make
any assumption that my class is not involved in inheritance.]

>     "Problem 1: It Can Slice Objects."
That would only happen if the destructor ~A() is declared
as virtual. Mine is not.
If the class would be inherited, it will not cause the problem either.

If the destructor would be virtual then the code naturally with
explicit resolution [this->A::~A()] will destroy and reconstruct
only sub-object A.

>     "Problem 2: It's Not Exception Safe"
There are no code between destruction and construction.
The destructor, I assumed, does not throw.
New operator does not throw because it is a placement new.
The constructor also does not throw because it is
implicitly-declared.

In more sophisticated case when A(A&) can throw, we have just
to handle the exception in the body of operator=.

>     "Problem 3: It Changes Normal Lifetimes"
Unfortunately, I do not know what mutex is and what means to
lock the class. I suspect it is not the case in my example.
If the class is inherited and the destructor is non-virtual
(as we have), then nothing wrong will happen whatever the
derived class does.

>     "Problem 4: It Can Still Break Derived Classes"
My class does not have data members, so it cannot.

In other case if you would have data members, then I'd say:
If you are digging base members, you have to be aware of
their indirect changing. In your example:
 const B & B::operator=( const B & x ) {
   // Modify a base classes member. // I know what and why I do this
   A::operator=( x );   // Oops, I am not sure how this works
   // Other assignments.
   return *this;
 }
it would be suspicious.

>     "Problem 5: this != &x"
My operator= does test this. It is exception safe and is not dangerous,
because 1) it is not virtual, and 2) operator& is not overloaded (even
if it would be in a derived class it will not affect the current
definition
of operator=).

A comment about
>     "Elegant Copy Assignment"
Here I cannot see a point to pass argument as a 'const A&' and
then create a new temporary (unless there are some other subtle
reasons).
I would write:
 const A & A::operator=(A x) {
    Swap( x );
    return *this;
 };
It has more logic because a copy will be created outside operator=;
and if the creation fails it will not get inside oprator=.
Sometimes it is easy to forget that operator=, in contrast to
the copy constructor, can take a non-reference argument.

Anyway, the above problems can be waiting for us.

And the last comment about your first example:
>     "This idiom is expressing copy assignment in terms of copy
construction." Better
> code follows.
>
>     class B : public A {
>         // more members
>         const B & operator=( const B & x ) {
>             A::operator=( x );
>             // Other assignments.
>             return *this;
>         }
>     };
This does not show "expressing copy assignment in terms of copy
construction".
It is expressing copy assignment in terms of copy assignment of a base
class.

I do not think that our Questions and Answers should be treated as
recommendations
of good practice. Simple examples cannot be guidelines.
>From the other side, it is always beneficial to highlight  weak points,
and express different opinions.

Thank you again, Mulray.

 - Oleg

Date: Mon, 22 Jul 2002 10:26:32 +0930
From: Oleg Mikusha <maz@milevena.com>

Oleg Mikusha wrote:
>
...
> If the destructor would be virtual then the code naturally with
> explicit resolution [this->A::~A()] will destroy and reconstruct
> only sub-object A.
...

This is not true.

In a polymorphic object (regardless the destructor is virtual or not)
all sub-objects (in non-multiple inheritance) share one common member -
a pointer to vtbl. Destruction and reconstruction of any sub-object
fraught with inappropriate modification of the vtbl pointer.
That behaviour can be seen in

struct B { virtual void f(); };
struct D : B { void f(); }
. . .
(new(new D) B) -> f(); // calls B::f

in spite of that the real object is D. (*)
Constructor B replaces the pointer to vtbl with its own value.

This flaw is not mentioned in Sutter's book "Exceptional C++",
item 41.

(*) Here is no destruction of the B sub-object, but it does not matter
since the destructor is implicitly defined and does not do anything
(**).

(**) Actually it does. It also replaces the vtbl pointer. But this is a
different thing.

 - Oleg


Q2

Date: Tue, 09 Jul 2002 13:41:55 +0930
From: Pretik Krykiferh <pkhasnab@udr.zhop.brr.con>

w.r.t Q 2 my preference is
using
<type> const <var>
instead of
const <type> <var>
reading from right to left the second one is much clearer.
const int *p; // p is  pointer to an integer which is a constant
int const *p; // p is a pointer to a constant integer  (or even better -> *p
is
a constant integer to avoid interpreting "int const *p , q;" wrongly)

Your typedef problem would go

Pretik

Date: 09 Jul 2002 14:46:02 +0930
From: Peter Durle <Peter.Durle@Milevena.com>

Without wishing to pre-empt the reason for the quiz question, there is
a world of difference between the two, it's really not a matter of
"preference".

const int       *p;   // can't assign to *p, but can change p
      int const *q;   // can assign to *q, but can't change q
const int const *r;   // can't assign to *r, nor change r

PCB

Date: Tue, 09 Jul 2002 15:16:52 +0930
From: Murlray Taylor <mtaylor@udr.zhop.brr.con>

Peter Durle wrote:

<original article snipped>

I'm glad someone pointed this out succinctly.
For bonus points do we call the above
    const pointer
    point to const
    const ptr to const
respectively???

I got caught by something similar last year when dealing with 'const'
references in C++ (which is actually redundant, they should be referred to
as "references to const").
A good article on a the problem with const in C++ can be found below.
http://www.embedded.com/story/OEG20010222S0050

Matt (no longer wondering why he prefers HW problems...)

> PCB


Date: Tue, 09 Jul 2002 15:53:42 +0930
From: Oleg Mikusha <maz@milevena.com>

Just a citation from this article:

        As I mentioned earlier, pointer declarations let you declare either
        a "pointer to const" or a "const pointer." For example:

        int const *p = &i;

        declares p as an object of type "pointer to const int, whereas:

        int *const q = &j;

        declares q as an object of type "const pointer to int." In the
latter
        declaration, the key word const appears in the declarator.

End of citation

 - Oleg


Date: Tue, 09 Jul 2002 15:44:37 +0930
From: Pretik Krykiferh <pkhasnab@udr.zhop.brr.con>

Peter Durle wrote:

HUH

const int *p;
and
int const *p;
has NO difference.
This means p is a varying pointer to constant data

int * const p
This means p is a constant pointer to varying data

const int * const p;
or
int const * const p;
This means p is a constant pointer to constant data.

and the trivial
int *p;
This means p is a varying  pointer to varying data

const int const *r;
is syntax error

const and volatile are modifiers of the type and should be read from right
to
left.

Pretik


Date: 09 Jul 2002 16:23:57 +0930
From: Peter Durle <Peter.Durle@Milevena.com>

I beg your pardon, of course in the q and r cases the * should precede
the const.  I had misread the order of your original post's examples,
and in that case I respectfully disagree with your preference of
<type> const <var>.

Particularly after misreading it just then, it seems to me that

        int const *q;

is too easily misinterpreted as

        int * const q;

And even though the advice for interpreting these things is "read them
from right to left", as an English speaker I will still read from left
to right.  Or perhaps I'm just trying to save my blushes?

PCB

Date: Tue, 09 Jul 2002 16:33:03 +0930
From: Pretik Krykiferh <pkhasnab@udr.zhop.brr.con>


But we are discussing a programming language with a more formal grammar
and different rules than a natural language as english.
Or maybe its just me because I'm used to different grammar than english (my
native language)
and can perhaps take a bit more dose of different rules.
But nothing to worry , I would have thought like that too years back. Few
years
programming in C & C++ and your brain cells will change :)

Pretik


Date: 09 Jul 2002 16:46:19 +0930
From: Peter Durle <Peter.Durle@Milevena.com>

Pretik Krykiferh <pkhasnab@udr.zhop.brr.con> writes:
>
> But we are discussing a programming language with a more formal grammar
> and different rules than a natural language as english.

That's true, but things like operator associativity/precedence is such
a common source of bugs (for schmucks like me, in any case) that
someone coined the pithy maxim:

        Multiplication precedes addition,
        Use parenthesis for everything else.

(Hmm, can we trust even this C advice in the overloaded operator world
of C++?).  I need all the help I can get - it's "const int" for me,
and for Stroustrup, I notice.

PCB

Date: Tue, 09 Jul 2002 16:51:01 +0930
From: Pretik Krykiferh <pkhasnab@udr.zhop.brr.con>

To tell you truth I also write const int;
The advise was for those who may have the difficulty with the typedef.
And
operator associativity/precedence  cannot be changed by overloading.
They are hardcoded in language spec.

Pretik


Date: Wed, 10 Jul 2002 09:57:34 +0930
From: Andrew booly <andrew.booly@milevena.com>

Peter Durle wrote:

> Particularly after misreading it just then, it seems to me that
>
>         int const *q;
>
> is too easily misinterpreted as
>
>         int * const q;

and therein lies an a point so *important* it's worth dragging
right out into the open.

"If it's potentially misleading to read - don't do it that way"

When some poor recently added team member comes along to do
maintenance on your code, they are already at the disadvantage
of not being as familiar with the content as you were when you
wrote it.  Give them a break (after all, it's not usually your
most highly experienced technical people who do maintenance is
it?) and use constructs that are not easily misunderstood by
less experienced developers.

Slapper

Date: Wed, 10 Jul 2002 12:16:19 +1000
From: Mulray Hytoppee <mhounsel@syd.udr.zhop.brr.con>

For clarity you should always read and write pointer and constant qualifiers
right
to left.
    int * p;              // pointer<int> p
    int const * p;        // pointer<const int> p;
    int * const p;        // const pointer<int> p;
    int const * const p;  // const pointer<const int> p;

    const int * p;        // This is legal.
    const int const * p;  // This is illegal but why you ask the compiler?

I learn't this the hard way trying to express a pointer< const pointer< char
> >
which is
    char * const * p;

Because I thought const was read left to right I originally thought it was
    char const * * p;
That was a painful error, I had no idea why the compiler was complaining.


Q3


Date: Wed, 10 Jul 2002 16:43:23 +0930
From: Fulridq Peng <fpeng@udr.zhop.brr.con>

The memory overhead for class A is a pointer in the VTABLE pointing to
a typeinfo structure for A.

Fulridq


Date: Wed, 10 Jul 2002 17:10:24 +0930
From: Pretik Krykiferh <pkhasnab@udr.zhop.brr.con>

I send you already

Undefined
Your question has no information about the structure of class A , like does
it
have virtual function , does it have base class , if so how many etc.

The structure used for RTTI information and where it is placed is a
implementation detail and not mentioned in the ISO C++ standard.

But the overhead will be equal to the RTTI symbol information , exact size
unknown , may vary with platform and compiler

Pretik


Date: Thu, 11 Jul 2002 15:49:30 +0930
From: Lance wukly <lwukly@udr.zhop.brr.con>

I seem to remember a certain C++ implementation (called Visual v5 I
think) that had a nasty habit of creating a string off the heap that
contained the text name of the class of the object queried using RTTI
(if it was turned on as a compiler option). This string could NOT be
deleted without undefined behaviour. As such it produced lots of nasty
warnings that had to be ignored. I don't know whether this is still the
case. Just a factoid trivium for the interested reader. (Otherwise zero
for a class with no virtual methods and implementation defined for a
generalised class.)

Lance


Date: Fri, 12 Jul 2002 10:46:14 +0930
From: Pretik Krykiferh <pkhasnab@udr.zhop.brr.con>

BTW a good answer is in item 24 More Effective C++ by Scott Meyer.

Pretik


Q4


Q5


Date: Fri, 12 Jul 2002 13:27:35 +0930
From: Oleg Mikusha <maz@milevena.com>

Still waiting for your answers.
Take a chance.

http://www.udr.zhop.brr.con/~omikusha/question.html

See, what this program will print.

#include <iostream.h>

struct B{
        virtual void f(int x=4); //1
};

struct D : B{
        virtual void f(); //2
private:
        void f(int x=5); //3
};

void B::f(int x){ cout<<1<<x; }
void D::f(){ cout<<2; }
void D::f(int x){ cout<<3<<x; }

int main(){
 D d;
 B * p = &d;
 (*p).f();
}

 - Oleg


Q6


Q7


Date: Wed, 17 Jul 2002 10:56:09 +0930
From: Pretik Krykiferh <pkhasnab@udr.zhop.brr.con>

Oleg Mikusha wrote:

> This is a very difficult question.
> For real gurus.
>
> Still no good answer...

The word guru gave it away , a little search on "guru of the week" website
brought out the facts
http://www.gotw.ca/publications/mill17.htm

Its good to know that
"If this surprises you, you're not alone; it has surprised a lot of experts
in
its time."

Pretik

Date: Wed, 17 Jul 2002 13:05:06 +0930
From: Oleg Mikusha <maz@milevena.com>

Yes, this article was published in
C/C++ Users Journal, 19(7), July 2001.
But it is incorrect in the journal and
was corrected only on internet.

So even Herb Sutter can be wrong,
But do not blame me if you think the
question was too difficult.
It could easily be checked by feeding
into compiler, say GNU.
(VC6 and Sun4.1 do not take it)

 - Oleg


Date: Wed, 17 Jul 2002 13:14:24 +0930
From: Pretik Krykiferh <pkhasnab@udr.zhop.brr.con>

Mr.Sutter is correct on the website and his answer is same as yours.
I am not blaming , on the contrary difficult questions are very welcome to
me
Verifying using compiler is worthless as every compiler deviates from the
ISO C++
standard
in some way or other specially VC++ and especially in templates.

Pretik


Date: Tue, 16 Jul 2002 17:14:29 +0930
From: Pretik Krykiferh <pkhasnab@udr.zhop.brr.con>


Hmm
I replied earlier to you and that was then obviously wrong.
the function call is equivalent to
f(0)
now 0 is a valid value for T = integer so f1 is a possible one
but 0 is also valid for int* .
quite stumping , I will take one more shot tomorrow , keep this question
open
please.

Pretik


Q8


Q9


Date: Fri, 26 Jul 2002 13:19:21 +0930
From: Pretik Krykiferh <pkhasnab@udr.zhop.brr.con>

It took me a while to glance at the answers

But particularly Q9 , I couldn't make any relation between the Q and the A

1. There is no mention of the word "static" in the answer

So the question is highly misleading.
------------------------------------------------------
Q912

Alice says that while a static member function cannot be virtual, she knows
one
exception to this rule, when a static function undergo a polymorphic
resolution.

What does she have in mind?
[Hint: What static functions cannot be non-static?]

A9

She has in mind a static deallocation function.

Let us look at the following example

struct B{
        virtual ~B();
        void * operator new(size_t);
        void operator delete(void*);
};

struct D : B {
        ~D();
        void * operator new(size_t);
        void operator delete(void*);
}
. . .
B * p = new D;
delete p;

We know that the proper destructor ~D() is called for the object of D
pointed by
p. [Do not forget that ~B() is also called within ~D() for the B
sub-object.]
But which operator delete should be chosen, of B
or of D? What if memory allocation and deallocation functions work
differently
for B and D objects?

If B::operator delete would be chosen that would be very wrong, because the
object was created in the memory allocated by D::new, not B::new.

Conclusion: deallocator is chosen like a destructor in the class which
object
really is of. Since a deallocation function is static it cannot be virtual.
But
in case (only) when the destructor is virtual, the call to this
function is resolved dynamically.

Note, back to the question 5, a static check for the deallocation function
call
is done relying on the base class. So B::operator delete must be accessible
even
if D::operator delete is actually called.

Date: Wed, 30 Oct 2002 15:42:09 +1030
From: Oleg Mikusha <maz@milevena.com>

Pretik,

I have just noticed your reply.

A quote from the Standard (12.5.6)
"Any deallocation function for a class X is a
static member (even if not explicitly declared
static)."

 - Oleg


Q10


Home