Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

What is the rationale behind rule of three?

60 views
Skip to first unread message

Srinivas Nayak

unread,
Aug 27, 2010, 1:40:26 PM8/27/10
to
Hi,

Here is a program.

==============
#include <iostream>


using namespace std;


class vector
{
int *v;
int size;
public:
vector(int m)
{
v = new int[size = m];
for(int i=0; i<size; i++)
v[i] = 0;
}
vector (int *a)
{
for(int i=0; i<size; i++)
v[i] = a[i];
}
int operator*(vector &y)
{
int sum = 0;
for(int i=0; i<size; i++)
sum += this -> v[i] * y . v[i];
return sum;
}

};

int main()
{
int x[3] = {1,2,3};
int y[3] = {4,5,6};
vector v1(3);
vector v2(3);
v1 = x;
v2 = y;
int R = v1 * v2;
cout << "R = " << R;
return 0;


}
==========

It gives segmentation fault. I found value of size in vector (int *a)
is no more 3. First of all I surprised to see that why vector (int *a)
gets called!

This program suffers from the fact that it has no operator=()
function. So for
v1 = x;
constructor vector (int *a) is called to create a temporary object
(with x as parameter) and then default operator=() provided by the
compiler shall be called to copy the object to v1.

Why this temporary object will be created?

To my thinking, for v1=x, we see that, two operands are of dissimilar
type. So we must look for explicit user provided operator=() function.
If not found, then there must be an error. Why are we creating
temporary object of same type as v1?

If I am wrong. Is there any rationale behind it?

Sincerely,
Srinivas Nayak

--
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use
mailto:std...@netlab.cs.rpi.edu<std-c%2B%2...@netlab.cs.rpi.edu>
]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]

Daniel Krügler

unread,
Aug 27, 2010, 6:28:25 PM8/27/10
to
[ Please try to ensure that any followups are topical for comp.std.c++
-- mod/jad ]

On 27 Aug., 19:40, Srinivas Nayak <sinu.nayak2...@gmail.com> wrote:
> ==============
> #include <iostream>
>
> using namespace std;
>
> class vector
> {
> int *v;
> int size;
> public:
> vector(int m)
> {
> v = new int[size = m];
> for(int i=0; i<size; i++)
> v[i] = 0;
> }

Side note: While looking at this code, I wonder,
why std::vector<int> wasn't chosen as internal
representation of your vector class.

> vector (int *a)
> {
> for(int i=0; i<size; i++)
> v[i] = a[i];
> }

Here, the initialization of *both* v and size
is missing. I assume that v was supposed to be
allocated to fit the length of a.

> int operator*(vector &y)
> {
> int sum = 0;
> for(int i=0; i<size; i++)
> sum += this -> v[i] * y . v[i];
> return sum;
> }

This function is a proper candidate for const-correctness.
There is no reason why y should not be a reference to
const vector or that this operator* overload should not
be a const member function (Even better: Make it a
non-member friend function. You will probably want to
check (at least assert) that both vectors have the same
length.

> };
>
> int main()
> {
> int x[3] = {1,2,3};
> int y[3] = {4,5,6};
> vector v1(3);
> vector v2(3);
> v1 = x;
> v2 = y;
> int R = v1 * v2;
> cout << "R = " << R;
> return 0;
> }
>
> ==========
>
> It gives segmentation fault. I found value of size in vector (int *a)
> is no more 3. First of all I surprised to see that why vector (int *a)
> gets called!
>
> This program suffers from the fact that it has no operator=()
> function.

And since you mentioned the rule of three: It also misses to
define a destructor that frees all allocated memory and
a copy constructor.

> So for
> v1 = x;
> constructor vector (int *a) is called to create a temporary object
> (with x as parameter) and then default operator=() provided by the
> compiler shall be called to copy the object to v1.
>
> Why this temporary object will be created?

You have an implicit conversion context here. The compiler is
supposed to verify that "v1 = x" is an invalid expression before
choking. Within this context the following is checked:

1) Does an operator= overload exist, that accepts an array or
pointer to int? There is none.
2) Does another operator= overload exist, where an *implicit
conversion sequence* could be used? The compiler finds *only*
the implicitly declared vector& operator=(const vector&) and
looks now for possible implicit conversions that could convert
the int array to a vector. Only a restricted set of possible
conversions is considered. In your case the compiler finds:

(a) An single initial standard conversion (Array-to-pointer
conversion)
(b) A single user-defined conversion by means of a user-
defined non-explicit constructor

Therefore, several things go wrong here: First, the buggy
constructor vector(int*) is called. Second, the implicit operator=
does just a "flat" copy of the internal pointer and size value.

To solve your problems:

1) Define the copy constructor, the copy assignment operator and
the destructor of vector (Or replace your internal representation
by std::vector<int> which is strongly recommended given your
example).

2) Fix your constructor vector(int*)

3) Make the constructors vector(int) and vector(int*) *explicit,
e.g.:

explicit vector(int m)
{
...
}

This prevents that the constructor would be considered in
situations where implicit conversions are considered.

> To my thinking, for v1=x, we see that, two operands are of dissimilar
> type. So we must look for explicit user provided operator=() function.
> If not found, then there must be an error. Why are we creating
> temporary object of same type as v1?
>
> If I am wrong. Is there any rationale behind it?

The rationale is the concept of implicit conversion
sequences. In your example a single user-defined
conversion sequence exists that allows for this
sequence (only one user-defined conversion is
considered):

int[3] => int* => vector(int*)

The temporary object is part of this special conversion
via constructor. In other cases a conversion function
would be possible as well.

HTH & Greetings from Bremen,

Daniel Krügler


--
[ comp.std.c++ is moderated. To submit articles, try just posting with ]

[ your news-reader. If that fails, use mailto:std...@netlab.cs.rpi.edu]

Srinivas Nayak

unread,
Aug 30, 2010, 3:32:27 PM8/30/10
to
Dear All,


> > ==============
> > #include <iostream>
>
> > using namespace std;
>
> > class vector
> > {
> > int *v;
> > int size;
> > public:
> > vector(int m)
> > {
> > v = new int[size = m];
> > for(int i=0; i<size; i++)
> > v[i] = 0;
> > }
>
> Side note: While looking at this code, I wonder,
> why std::vector<int> wasn't chosen as internal
> representation of your vector class.

I understand your point. Actually, I was not interested to use vector
clas for any particular problem, rather it was an attempt to show the
behaviour of the code which seem counter-intuitive.

>
> > vector (int *a)
> > {
> > for(int i=0; i<size; i++)
> > v[i] = a[i];
> > }
>
> Here, the initialization of *both* v and size
> is missing. I assume that v was supposed to be
> allocated to fit the length of a.

You are right, but to me, as a programmer, my intution says that I
have already created v1, now I am interested to copy a vector to this
object, why another constructor be called?

>
> > int operator*(vector &y)
> > {
> > int sum = 0;
> > for(int i=0; i<size; i++)
> > sum += this -> v[i] * y . v[i];
> > return sum;
> > }
> > };
>
> This function is a proper candidate for const-correctness.
> There is no reason why y should not be a reference to
> const vector or that this operator* overload should not
> be a const member function (Even better: Make it a
> non-member friend function. You will probably want to
> check (at least assert) that both vectors have the same
> length.

This code was just there to see multiplication at work, now can be
discarded from our current interest/discussion.

> > int main()
> > {
> > int x[3] = {1,2,3};
> > int y[3] = {4,5,6};
> > vector v1(3);
> > vector v2(3);
> > v1 = x;
> > v2 = y;
> > int R = v1 * v2;
> > cout << "R = " << R;
> > return 0;
> > }
>
> > ==========
>
> > It gives segmentation fault. I found value of size in vector (int *a)
> > is no more 3. First of all I surprised to see that why vector (int *a)
> > gets called!
>
> > This program suffers from the fact that it has no operator=()
> > function.
>
> And since you mentioned the rule of three: It also misses to
> define a destructor that frees all allocated memory and
> a copy constructor.

Intentionally destructor was omitted. For the current interest, I
suppose, we can ignore it.

>
> > So for
> > v1 = x;
> > constructor vector (int *a) is called to create a temporary object
> > (with x as parameter) and then default operator=() provided by the
> > compiler shall be called to copy the object to v1.
>
> > Why this temporary object will be created?
>
> You have an implicit conversion context here. The compiler is
> supposed to verify that "v1 = x" is an invalid expression before
> choking. Within this context the following is checked:
>
> 1) Does an operator= overload exist, that accepts an array or
> pointer to int? There is none.

My point was, from a language point of view, we shall stop here. Why
shall we look for something else? Even though we may get some way out,
shall we say that, that was the intension of the programmer? Such
things are refused by strong type checking, I believe.

> 2) Does another operator= overload exist, where an *implicit
> conversion sequence* could be used? The compiler finds *only*
> the implicitly declared vector& operator=(const vector&) and
> looks now for possible implicit conversions that could convert
> the int array to a vector. Only a restricted set of possible
> conversions is considered. In your case the compiler finds:

How a programmer will know -"Only a restricted set of possible
conversions is considered."- which are there in that set?

>
> (a) An single initial standard conversion (Array-to-pointer
> conversion)
> (b) A single user-defined conversion by means of a user-
> defined non-explicit constructor
>
> Therefore, several things go wrong here: First, the buggy
> constructor vector(int*) is called. Second, the implicit operator=
> does just a "flat" copy of the internal pointer and size value.
>
> To solve your problems:
>
> 1) Define the copy constructor, the copy assignment operator and
> the destructor of vector (Or replace your internal representation
> by std::vector<int> which is strongly recommended given your
> example).
>
> 2) Fix your constructor vector(int*)
>
> 3) Make the constructors vector(int) and vector(int*) *explicit,
> e.g.:
>
> explicit vector(int m)
> {
> ...
> }
>
> This prevents that the constructor would be considered in
> situations where implicit conversions are considered.
>
> > To my thinking, for v1=x, we see that, two operands are of dissimilar

> > type. So we must look for explicit user provided operator=() function=


> > If not found, then there must be an error. Why are we creating
> > temporary object of same type as v1?
>
> > If I am wrong. Is there any rationale behind it?
>
> The rationale is the concept of implicit conversion
> sequences. In your example a single user-defined
> conversion sequence exists that allows for this
> sequence (only one user-defined conversion is
> considered):
>
> int[3] => int* => vector(int*)
>
> The temporary object is part of this special conversion
> via constructor. In other cases a conversion function
> would be possible as well.

I understand the idea. It is better that we have some sort of implicit
conversions which are helpful sometimes, like,
1. int to float,
2. array to pointer
3. implicit copy constructor etc..

But do you believe that, finding some way out for an implicit
conversion is a good thing? In such a case, it is upto the language
how it plans to execute a programmer's program and not upto the
programmer to write a program that he wishes to be performed!

>
> HTH & Greetings from Bremen,
>

> Daniel Kr=FCgler


>
> --
> [ comp.std.c++ is moderated. To submit articles, try just posting with ]

> [ your news-reader. If that fails, use mailto:std-...@netlab.cs.rpi.edu]


> [ --- Please see the FAQ before posting. --- ]

> [ FAQ:http://www.comeaucomputing.com/csc/faq.html ]-
Hide quoted text -
>
> - Show quoted text -- Hide quoted text -
>
> - Show quoted text -

--
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use

mailto:std...@netlab.cs.rpi.edu<std-c%2B%2...@netlab.cs.rpi.edu>

Daniel Krügler

unread,
Aug 30, 2010, 10:15:43 PM8/30/10
to
On 30 Aug., 21:32, Srinivas Nayak <sinu.nayak2...@gmail.com> wrote:

[..]

> > > vector (int *a)
> > > {
> > > for(int i=0; i<size; i++)
> > > v[i] = a[i];
> > > }
>
> > Here, the initialization of *both* v and size
> > is missing. I assume that v was supposed to be
> > allocated to fit the length of a.
>
> You are right, but to me, as a programmer, my intution says that I
> have already created v1, now I am interested to copy a vector to this
> object, why another constructor be called?

Due to historical reasons. In the beginning of the standardization
process several schools found implicit conversions nice. Later
it was found out, that these might not always be useful, and the
idea of the "explicit" constructor was born. Recently, this idea
was extended to conversion functions as well, so you can now
also ensure that those are only considered in explicit invocation
contexts (e.g. direct initialization).

> > 1) Does an operator= overload exist, that accepts an array or
> > pointer to int? There is none.
>
> My point was, from a language point of view, we shall stop here. Why
> shall we look for something else? Even though we may get some way out,
> shall we say that, that was the intension of the programmer? Such
> things are refused by strong type checking, I believe.

It weakens these rules, yes. But sometimes such implicit conversions
are very useful and safe, so your opinion applies to some restricted
applications, not to all.

> > 2) Does another operator= overload exist, where an *implicit
> > conversion sequence* could be used? The compiler finds *only*
> > the implicitly declared vector& operator=(const vector&) and
> > looks now for possible implicit conversions that could convert
> > the int array to a vector. Only a restricted set of possible
> > conversions is considered. In your case the compiler finds:
>
> How a programmer will know -"Only a restricted set of possible
> conversions is considered."- which are there in that set?

The exact rules are given in sub-clause [over.best.ics]. Among
a lot of details, one essential rule is the following:

"When the parameter type is not a reference, the implicit
conversion sequence models a copy-initialization of the
parameter from the argument expression. The implicit
conversion sequence is the one required to convert the
argument expression to a prvalue of the type of the parameter"

> > The rationale is the concept of implicit conversion
> > sequences. In your example a single user-defined
> > conversion sequence exists that allows for this
> > sequence (only one user-defined conversion is
> > considered):
>
> > int[3] => int* => vector(int*)
>
> > The temporary object is part of this special conversion
> > via constructor. In other cases a conversion function
> > would be possible as well.
>
> I understand the idea. It is better that we have some sort of implicit
> conversions which are helpful sometimes, like,
> 1. int to float,
> 2. array to pointer
> 3. implicit copy constructor etc..
>
> But do you believe that, finding some way out for an implicit
> conversion is a good thing? In such a case, it is upto the language
> how it plans to execute a programmer's program and not upto the
> programmer to write a program that he wishes to be performed!

If we would have started to design C++ from today on, there
is a good chance, that all constructors and conversion functions
were "explicit" by default, but a user could make the "implicit".
Alas, the time cannot be turned back and we have to live with
the current state. This means that we have to be careful in
regard to implicit constructors (except for the copy/move
constructor). It is a safer rule to make constructors explicit,
where some form of conversion is involved (i.e. any constructor
with an argument type U different from the actual type T).

HTH & Greetings from Bremen,

Daniel Krügler


--
[ comp.std.c++ is moderated. To submit articles, try just posting with ]

[ your news-reader. If that fails, use mailto:std...@netlab.cs.rpi.edu]

0 new messages