Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]
Groups > comp.lang.c++ > #7337 > unrolled thread
| Started by | Anonymous <anonymous@no.net> |
|---|---|
| First post | 2011-07-02 00:19 +0200 |
| Last post | 2011-07-06 01:28 +0200 |
| Articles | 20 on this page of 22 — 5 participants |
Back to article view | Back to comp.lang.c++
Virtual functions in constructors. Is it possible? Anonymous <anonymous@no.net> - 2011-07-02 00:19 +0200
Re: Virtual functions in constructors. Is it possible? "Alf P. Steinbach /Usenet" <alf.p.steinbach+usenet@gmail.com> - 2011-07-02 00:37 +0200
Re: Virtual functions in constructors. Is it possible? Anonymous <anonymous@no.net> - 2011-07-02 01:19 +0200
Re: Virtual functions in constructors. Is it possible? "Alf P. Steinbach /Usenet" <alf.p.steinbach+usenet@gmail.com> - 2011-07-02 08:50 +0200
Re: Virtual functions in constructors. Is it possible? Anonymous <anonymous@no.net> - 2011-07-02 10:50 +0200
Re: Virtual functions in constructors. Is it possible? "Alf P. Steinbach /Usenet" <alf.p.steinbach+usenet@gmail.com> - 2011-07-02 11:17 +0200
Re: Virtual functions in constructors. Is it possible? Anonymous <anonymous@no.net> - 2011-07-02 11:25 +0200
Re: Virtual functions in constructors. Is it possible? Anonymous <anonymous@no.net> - 2011-07-02 11:31 +0200
Re: Virtual functions in constructors. Is it possible? "Alf P. Steinbach /Usenet" <alf.p.steinbach+usenet@gmail.com> - 2011-07-02 12:00 +0200
Re: Virtual functions in constructors. Is it possible? Werner <werasm@gmail.com> - 2011-07-04 01:10 -0700
Re: Virtual functions in constructors. Is it possible? Anonymous <anonymous@no.net> - 2011-07-04 20:41 +0200
Re: Virtual functions in constructors. Is it possible? "Alf P. Steinbach /Usenet" <alf.p.steinbach+usenet@gmail.com> - 2011-07-04 21:35 +0200
Re: Virtual functions in constructors. Is it possible? Leo Equinox Gaspard <ekinox1995@gmail.com> - 2011-07-04 23:59 +0200
Re: Virtual functions in constructors. Is it possible? "Alf P. Steinbach /Usenet" <alf.p.steinbach+usenet@gmail.com> - 2011-07-05 11:09 +0200
Re: Virtual functions in constructors. Is it possible? Leo Equinox Gaspard <ekinox1995@gmail.com> - 2011-07-05 13:15 +0200
Re: Virtual functions in constructors. Is it possible? "Alf P. Steinbach /Usenet" <alf.p.steinbach+usenet@gmail.com> - 2011-07-05 14:51 +0200
Re: Virtual functions in constructors. Is it possible? Leo Equinox Gaspard <ekinox1995@gmail.com> - 2011-07-05 19:55 +0200
Re: Virtual functions in constructors. Is it possible? Anonymous <anonymous@no.net> - 2011-07-05 20:46 +0200
Re: Virtual functions in constructors. Is it possible? "Alf P. Steinbach /Usenet" <alf.p.steinbach+usenet@gmail.com> - 2011-07-05 21:23 +0200
Re: Virtual functions in constructors. Is it possible? Werner <werasm@gmail.com> - 2011-07-06 00:14 -0700
Re: Virtual functions in constructors. Is it possible? Virchanza <virtual@lavabit.com> - 2011-07-05 14:58 -0700
Re: Virtual functions in constructors. Is it possible? "Alf P. Steinbach /Usenet" <alf.p.steinbach+usenet@gmail.com> - 2011-07-06 01:28 +0200
Page 1 of 2 [1] 2 Next page →
| From | Anonymous <anonymous@no.net> |
|---|---|
| Date | 2011-07-02 00:19 +0200 |
| Subject | Virtual functions in constructors. Is it possible? |
| Message-ID | <iulh53$8t7$1@speranza.aioe.org> |
Is it possible to call a virtual function as Init() in a constructor?
So that I could write something like this:
{
Base a ; // calls Base::Init
Derived b ; // calls Derived::Init
}
Ok, before answering I must admit you that I have a solution, but I am
just curious to know how many of you know the trick. When I saw it the
first time, I was really surprised about the power of C++ :)
[toc] | [next] | [standalone]
| From | "Alf P. Steinbach /Usenet" <alf.p.steinbach+usenet@gmail.com> |
|---|---|
| Date | 2011-07-02 00:37 +0200 |
| Message-ID | <iulhlm$tji$2@dont-email.me> |
| In reply to | #7337 |
* Anonymous, on 02.07.2011 00:19:
> Is it possible to call a virtual function as Init() in a constructor?
>
> So that I could write something like this:
>
> {
> Base a ; // calls Base::Init
> Derived b ; // calls Derived::Init
> }
>
> Ok, before answering I must admit you that I have a solution, but I am just
> curious to know how many of you know the trick. When I saw it the first time, I
> was really surprised about the power of C++ :)
See the FAQ
<url: http://www.parashift.com/c++-faq-lite/strange-inheritance.html#faq-23.6>
Cheers & hth.,
- Alf
--
blog at <url: http://alfps.wordpress.com>
[toc] | [prev] | [next] | [standalone]
| From | Anonymous <anonymous@no.net> |
|---|---|
| Date | 2011-07-02 01:19 +0200 |
| Message-ID | <iulkmt$fvq$1@speranza.aioe.org> |
| In reply to | #7338 |
> See the FAQ > > <url: > http://www.parashift.com/c++-faq-lite/strange-inheritance.html#faq-23.6> That is an ugly solution in my opinion. I think there is something much more elegant.. :)
[toc] | [prev] | [next] | [standalone]
| From | "Alf P. Steinbach /Usenet" <alf.p.steinbach+usenet@gmail.com> |
|---|---|
| Date | 2011-07-02 08:50 +0200 |
| Message-ID | <iumejo$kv6$1@dont-email.me> |
| In reply to | #7339 |
* Anonymous, on 02.07.2011 01:19: >> See the FAQ >> >> <url: http://www.parashift.com/c++-faq-lite/strange-inheritance.html#faq-23.6> > > That is an ugly solution in my opinion. It's not "a" solution, it's a discussion of the possible ways in current C++. > I think there is something much more elegant.. :) Well, spit it out. Cheers & hth., - Alf -- blog at <url: http://alfps.wordpress.com>
[toc] | [prev] | [next] | [standalone]
| From | Anonymous <anonymous@no.net> |
|---|---|
| Date | 2011-07-02 10:50 +0200 |
| Message-ID | <iumm5c$di7$1@speranza.aioe.org> |
| In reply to | #7351 |
Alf P. Steinbach /Usenet ha scritto: > * Anonymous, on 02.07.2011 01:19: >>> See the FAQ >>> >>> <url: >>> http://www.parashift.com/c++-faq-lite/strange-inheritance.html#faq-23.6> >> >> That is an ugly solution in my opinion. > > It's not "a" solution, it's a discussion of the possible ways in current > C++. Ok, to be precise, the ways mentioned in the FAQ have the following limitations: "In this variation, the code that is creating the object *explicitly executes both phases*. When executing Phase I, the code creating the object either knows the object's exact class (e.g., new Derived() or perhaps a local Derived object), or doesn't know the object's exact class (e.g., the virtual constructor idiom or some other factory). The "doesn't know" case is strongly preferred when you want to make it easy to plug-in new derived classes." and "The next approach does not rely on a two-phase initialization, instead using a second hierarchy whose only job is to house methods foo() and bar(). *This approach doesn't always work, and in particular it doesn't work in cases when foo() and bar() need to access the instance data declared in Derived*, but it is conceptually quite simple and clean and is commonly used." In a way or the other static member functions are used there, which make the approaches just "ugly". They key to the *perfect solution* is to profit by the lifetime of temporary objects passed to constructors: you can invoke a virtual function init() after an object has been constructed by calling it from a destructor of a temporary object passed to the constructor of the class that has to be initialized.
[toc] | [prev] | [next] | [standalone]
| From | "Alf P. Steinbach /Usenet" <alf.p.steinbach+usenet@gmail.com> |
|---|---|
| Date | 2011-07-02 11:17 +0200 |
| Message-ID | <iumn7a$veh$1@dont-email.me> |
| In reply to | #7364 |
* Anonymous, on 02.07.2011 10:50: > Alf P. Steinbach /Usenet ha scritto: >> * Anonymous, on 02.07.2011 01:19: >>>> See the FAQ >>>> >>>> <url: http://www.parashift.com/c++-faq-lite/strange-inheritance.html#faq-23.6> >>> >>> That is an ugly solution in my opinion. >> >> It's not "a" solution, it's a discussion of the possible ways in current C++. > > Ok, to be precise, the ways mentioned in the FAQ have the following limitations: > > "In this variation, the code that is creating the object *explicitly executes > both phases*. When executing Phase I, the code creating the object either knows > the object's exact class (e.g., new Derived() or perhaps a local Derived > object), or doesn't know the object's exact class (e.g., the virtual constructor > idiom or some other factory). The "doesn't know" case is strongly preferred when > you want to make it easy to plug-in new derived classes." > > and > > > "The next approach does not rely on a two-phase initialization, instead using a > second hierarchy whose only job is to house methods foo() and bar(). *This > approach doesn't always work, and in particular it doesn't work in cases when > foo() and bar() need to access the instance data declared in Derived*, but it is > conceptually quite simple and clean and is commonly used." > > In a way or the other static member functions are used there, which make the > approaches just "ugly". > > They key to the *perfect solution* is to profit by the lifetime of temporary > objects passed to constructors: you can invoke a virtual function init() after > an object has been constructed by calling it from a destructor of a temporary > object passed to the constructor of the class that has to be initialized. So, if I do p = new YourClass() and init() fails, do I have a memory leak or not? But I think the scheme that you're suggesting should be in that FAQ item. I didn't think of it when I convinced Marshall to add that FAQ item (and neither did he, or AFAIK anyone else), sorry. Cheers & thanks, - Alf -- blog at <url: http://alfps.wordpress.com>
[toc] | [prev] | [next] | [standalone]
| From | Anonymous <anonymous@no.net> |
|---|---|
| Date | 2011-07-02 11:25 +0200 |
| Message-ID | <iumo6l$hs6$1@speranza.aioe.org> |
| In reply to | #7366 |
Alf P. Steinbach /Usenet ha scritto: > * Anonymous, on 02.07.2011 10:50: >> Alf P. Steinbach /Usenet ha scritto: >>> * Anonymous, on 02.07.2011 01:19: >>>>> See the FAQ >>>>> >>>>> <url: >>>>> http://www.parashift.com/c++-faq-lite/strange-inheritance.html#faq-23.6> >>>>> >>>> >>>> That is an ugly solution in my opinion. >>> >>> It's not "a" solution, it's a discussion of the possible ways in >>> current C++. >> >> Ok, to be precise, the ways mentioned in the FAQ have the following >> limitations: >> >> "In this variation, the code that is creating the object *explicitly >> executes >> both phases*. When executing Phase I, the code creating the object >> either knows >> the object's exact class (e.g., new Derived() or perhaps a local Derived >> object), or doesn't know the object's exact class (e.g., the virtual >> constructor >> idiom or some other factory). The "doesn't know" case is strongly >> preferred when >> you want to make it easy to plug-in new derived classes." >> >> and >> >> >> "The next approach does not rely on a two-phase initialization, >> instead using a >> second hierarchy whose only job is to house methods foo() and bar(). >> *This >> approach doesn't always work, and in particular it doesn't work in >> cases when >> foo() and bar() need to access the instance data declared in Derived*, >> but it is >> conceptually quite simple and clean and is commonly used." >> >> In a way or the other static member functions are used there, which >> make the >> approaches just "ugly". >> >> They key to the *perfect solution* is to profit by the lifetime of >> temporary >> objects passed to constructors: you can invoke a virtual function >> init() after >> an object has been constructed by calling it from a destructor of a >> temporary >> object passed to the constructor of the class that has to be initialized. > > So, if I do > > p = new YourClass() > > and init() fails, do I have a memory leak or not? no, because init() is called from a destructor of an object passed as argument to the constructor. From top down, if init() throws an exception, then the destruction fails, then the construction fails.. > > But I think the scheme that you're suggesting should be in that FAQ item. where exactly?
[toc] | [prev] | [next] | [standalone]
| From | Anonymous <anonymous@no.net> |
|---|---|
| Date | 2011-07-02 11:31 +0200 |
| Message-ID | <iumohe$hs6$2@speranza.aioe.org> |
| In reply to | #7367 |
Anonymous ha scritto: >> But I think the scheme that you're suggesting should be in that FAQ item. > > where exactly? sorry forget the question, i misread.
[toc] | [prev] | [next] | [standalone]
| From | "Alf P. Steinbach /Usenet" <alf.p.steinbach+usenet@gmail.com> |
|---|---|
| Date | 2011-07-02 12:00 +0200 |
| Message-ID | <iumpmn$bd3$1@dont-email.me> |
| In reply to | #7367 |
* Anonymous, on 02.07.2011 11:25: > Alf P. Steinbach /Usenet ha scritto: >>> >>> They key to the *perfect solution* is to profit by the lifetime of temporary >>> objects passed to constructors: you can invoke a virtual function init() after >>> an object has been constructed by calling it from a destructor of a temporary >>> object passed to the constructor of the class that has to be initialized. >> >> So, if I do >> >> p = new YourClass() >> >> and init() fails, do I have a memory leak or not? > > no, because init() is called from a destructor of an object passed as argument > to the constructor. From top down, if init() throws an exception, then the > destruction fails, then the construction fails.. I think you're mistaken. Could you post a complete smallest possible example? Cheers & hth., - Alf -- blog at <url: http://alfps.wordpress.com>
[toc] | [prev] | [next] | [standalone]
| From | Werner <werasm@gmail.com> |
|---|---|
| Date | 2011-07-04 01:10 -0700 |
| Message-ID | <e92f861a-e982-4dec-9f95-bf8286a15187@q1g2000vbj.googlegroups.com> |
| In reply to | #7367 |
On Jul 2, 11:25 am, Anonymous <anonym...@no.net> wrote: > no, because init() is called from a destructor of an object passed as > argument to the constructor. From top down, if init() throws an > exception, then the destruction fails, then the construction fails.. Init() may not fail by throwing, since it's called from a destructor, and throwing would imply terminating (15.2.3). Kind Regards, Werner
[toc] | [prev] | [next] | [standalone]
| From | Anonymous <anonymous@no.net> |
|---|---|
| Date | 2011-07-04 20:41 +0200 |
| Message-ID | <iut1gm$4dn$1@speranza.aioe.org> |
| In reply to | #7431 |
Werner ha scritto:
> On Jul 2, 11:25 am, Anonymous <anonym...@no.net> wrote:
>
>
>> no, because init() is called from a destructor of an object passed as
>> argument to the constructor. From top down, if init() throws an
>> exception, then the destruction fails, then the construction fails..
>
> Init() may not fail by throwing, since it's called from a
> destructor, and throwing would imply terminating (15.2.3).
>
> Kind Regards,
>
> Werner
Ok, after all I think that if necessary init() might handle failures
inside its body.
class VirtualBase {
protected:
class Initializer;
friend class Initializer;
class Initializer {
public:
Initializer() {
std::cout << "Initializer" << std::endl;
p = 0;
}
~Initializer() {
std::cout << "~Initializer" << std::endl;
if (p)
p->Init(); // call the virtual function
}
// might be private if VirtualBase is declared as friend...
void Register(VirtualBase* b) const {
p = b;
}
private:
mutable VirtualBase* p;
// private and not implemented
Initializer(const Initializer&);
void operator =(const Initializer&);
};
public:
VirtualBase(const Initializer& i) {
std::cout << "VirtualBase" << std::endl;
i.Register(this);
}
private:
virtual void Init() = 0;
// will be called immediately after the constructor
// of the most derived class
};
// This is the actual hierarchy
class Base : public virtual VirtualBase {
public:
Base(const VirtualBase::Initializer& i = Initializer()) :
VirtualBase(i) {
std::cout << "Base" << std::endl;
}
~Base() {
std::cout << "~Base" << std::endl;
}
void Init() {
std::cout << "Base::Init()" << std::endl;
}
};
class Derived : public Base {
public:
// Derived() : Base() {} // compile-time error as wanted
Derived(const VirtualBase::Initializer& i = Initializer()) :
Base(i), /* Base(i) is optional...*/
VirtualBase(i)
{
std::cout << "Derived" << std::endl;
}
~Derived() {
std::cout << "~Derived" << std::endl;
}
void Init() {
std::cout << "Derived::Init()" << std::endl;
}
};
int main() {
Base x; // calls Base::Init
Derived y ; // calls Derived::Init
return( 0 ) ;
}
[toc] | [prev] | [next] | [standalone]
| From | "Alf P. Steinbach /Usenet" <alf.p.steinbach+usenet@gmail.com> |
|---|---|
| Date | 2011-07-04 21:35 +0200 |
| Message-ID | <iut4g4$jio$1@dont-email.me> |
| In reply to | #7447 |
* Anonymous, on 04.07.2011 20:41:
>
> class VirtualBase {
> protected:
> class Initializer;
> friend class Initializer;
>
> class Initializer {
> public:
>
> Initializer() {
> std::cout << "Initializer" << std::endl;
> p = 0;
> }
>
> ~Initializer() {
> std::cout << "~Initializer" << std::endl;
> if (p)
> p->Init(); // call the virtual function
> }
> // might be private if VirtualBase is declared as friend...
>
> void Register(VirtualBase* b) const {
> p = b;
> }
> private:
> mutable VirtualBase* p;
> // private and not implemented
> Initializer(const Initializer&);
> void operator =(const Initializer&);
> };
> public:
>
> VirtualBase(const Initializer& i) {
> std::cout << "VirtualBase" << std::endl;
> i.Register(this);
> }
> private:
> virtual void Init() = 0;
> // will be called immediately after the constructor
> // of the most derived class
> };
>
> // This is the actual hierarchy
>
> class Base : public virtual VirtualBase {
> public:
>
> Base(const VirtualBase::Initializer& i = Initializer()) : VirtualBase(i) {
> std::cout << "Base" << std::endl;
> }
>
> ~Base() {
> std::cout << "~Base" << std::endl;
> }
>
> void Init() {
> std::cout << "Base::Init()" << std::endl;
> }
> };
>
> class Derived : public Base {
> public:
> // Derived() : Base() {} // compile-time error as wanted
>
> Derived(const VirtualBase::Initializer& i = Initializer()) :
> Base(i), /* Base(i) is optional...*/
> VirtualBase(i)
> {
> std::cout << "Derived" << std::endl;
> }
>
> ~Derived() {
> std::cout << "~Derived" << std::endl;
> }
>
> void Init() {
> std::cout << "Derived::Init()" << std::endl;
> }
> };
>
>
> int main() {
> Base x; // calls Base::Init
> Derived y ; // calls Derived::Init
> return( 0 ) ;
> }
I reworked your example to show off the memory leak problem that I mentioned.
In addition this led me to realize one problem that I didn't think of earlier,
namely that in the event of a failing constructor, the Initializer destructor
will be calling a virtual method on a non-existent object, invoking UB.
Here's the reworked code, just play with the macro symbols NO_INIT_CALL,
FAIL_CONSTRUCTOR and FAIL_INIT:
<code>
#include <iostream>
#include <stdexcept>
#include <stdlib.h>
using namespace std;
namespace g {
int nObjectsAllocated = 0;
} // namespace g
class VirtualBase {
protected:
class Initializer;
friend class Initializer;
class Initializer {
public:
Initializer() {
cout << "Initializer" << endl;
p = 0;
}
~Initializer() {
cout << "~Initializer" << endl;
#ifndef NO_INIT_CALL
if (p)
p->Init(); // call the virtual function
#endif
}
// might be private if VirtualBase is declared as friend...
void Register(VirtualBase* b) const {
p = b;
}
private:
mutable VirtualBase* p;
// private and not implemented
Initializer(const Initializer&);
void operator =(const Initializer&);
};
public:
VirtualBase(const Initializer& i) {
cout << "VirtualBase" << endl;
i.Register(this);
}
private:
virtual void Init() = 0;
// will be called immediately after the constructor
// of the most derived class
};
// This is the actual hierarchy
class Base : public virtual VirtualBase {
public:
static void* operator new( size_t size )
{
cout << "Allocation" << endl;
++g::nObjectsAllocated;
return ::operator new( size );
}
static void operator delete( void* p )
{
cout << "Deallocation" << endl;
--g::nObjectsAllocated;
return ::operator delete( p );
}
Base(const VirtualBase::Initializer& i = Initializer()) : VirtualBase(i) {
cout << "Base" << endl;
#ifdef FAIL_CONSTRUCTOR
throw runtime_error( "Oops, `Base::Base` failed." );
#endif
}
~Base() {
cout << "~Base" << endl;
}
void Init() {
cout << "Base::Init()" << endl;
#ifdef FAIL_INIT
throw runtime_error( "Oops, `Init` failed." );
#endif
}
};
int main()
{
int result = EXIT_FAILURE;
try
{
Base* p = new Base; // calls Base::Init
cout << endl;
delete p;
result = EXIT_SUCCESS;
}
catch( exception const& x )
{
cerr << "!" << x.what() << endl;
}
if( g::nObjectsAllocated > 0 )
{
cerr << "!Memory leak." << endl;
}
cout << "Finished." << endl;
return result;
}
</code>
As you can see the scheme, at least in its current incarnation, is not very
exception-friendly: invoking UB, and leaking memory.
Can it be made exception safe?
Cheers & hth.,
- Alf
--
blog at <url: http://alfps.wordpress.com>
[toc] | [prev] | [next] | [standalone]
| From | Leo Equinox Gaspard <ekinox1995@gmail.com> |
|---|---|
| Date | 2011-07-04 23:59 +0200 |
| Message-ID | <iutd2n$d9m$1@dont-email.me> |
| In reply to | #7452 |
Le 04/07/2011 21:35, Alf P. Steinbach /Usenet a écrit :
> * Anonymous, on 04.07.2011 20:41:
>>
>> class VirtualBase {
>> protected:
>> class Initializer;
>> friend class Initializer;
>>
>> class Initializer {
>> public:
>>
>> Initializer() {
>> std::cout << "Initializer" << std::endl;
>> p = 0;
>> }
>>
>> ~Initializer() {
>> std::cout << "~Initializer" << std::endl;
>> if (p)
>> p->Init(); // call the virtual function
>> }
>> // might be private if VirtualBase is declared as friend...
>>
>> void Register(VirtualBase* b) const {
>> p = b;
>> }
>> private:
>> mutable VirtualBase* p;
>> // private and not implemented
>> Initializer(const Initializer&);
>> void operator =(const Initializer&);
>> };
>> public:
>>
>> VirtualBase(const Initializer& i) {
>> std::cout << "VirtualBase" << std::endl;
>> i.Register(this);
>> }
>> private:
>> virtual void Init() = 0;
>> // will be called immediately after the constructor
>> // of the most derived class
>> };
>>
>> // This is the actual hierarchy
>>
>> class Base : public virtual VirtualBase {
>> public:
>>
>> Base(const VirtualBase::Initializer& i = Initializer()) :
>> VirtualBase(i) {
>> std::cout << "Base" << std::endl;
>> }
>>
>> ~Base() {
>> std::cout << "~Base" << std::endl;
>> }
>>
>> void Init() {
>> std::cout << "Base::Init()" << std::endl;
>> }
>> };
>>
>> class Derived : public Base {
>> public:
>> // Derived() : Base() {} // compile-time error as wanted
>>
>> Derived(const VirtualBase::Initializer& i = Initializer()) :
>> Base(i), /* Base(i) is optional...*/
>> VirtualBase(i)
>> {
>> std::cout << "Derived" << std::endl;
>> }
>>
>> ~Derived() {
>> std::cout << "~Derived" << std::endl;
>> }
>>
>> void Init() {
>> std::cout << "Derived::Init()" << std::endl;
>> }
>> };
>>
>>
>> int main() {
>> Base x; // calls Base::Init
>> Derived y ; // calls Derived::Init
>> return( 0 ) ;
>> }
>
> I reworked your example to show off the memory leak problem that I
> mentioned.
>
> In addition this led me to realize one problem that I didn't think of
> earlier, namely that in the event of a failing constructor, the
> Initializer destructor will be calling a virtual method on a
> non-existent object, invoking UB.
>
> Here's the reworked code, just play with the macro symbols NO_INIT_CALL,
> FAIL_CONSTRUCTOR and FAIL_INIT:
>
>
> <code>
> #include <iostream>
> #include <stdexcept>
> #include <stdlib.h>
> using namespace std;
>
> namespace g {
> int nObjectsAllocated = 0;
> } // namespace g
>
> class VirtualBase {
> protected:
> class Initializer;
> friend class Initializer;
>
> class Initializer {
> public:
>
> Initializer() {
> cout << "Initializer" << endl;
> p = 0;
> }
>
> ~Initializer() {
> cout << "~Initializer" << endl;
> #ifndef NO_INIT_CALL
> if (p)
> p->Init(); // call the virtual function
> #endif
> }
> // might be private if VirtualBase is declared as friend...
>
> void Register(VirtualBase* b) const {
> p = b;
> }
> private:
> mutable VirtualBase* p;
> // private and not implemented
> Initializer(const Initializer&);
> void operator =(const Initializer&);
> };
> public:
>
> VirtualBase(const Initializer& i) {
> cout << "VirtualBase" << endl;
> i.Register(this);
> }
> private:
> virtual void Init() = 0;
> // will be called immediately after the constructor
> // of the most derived class
> };
>
> // This is the actual hierarchy
>
> class Base : public virtual VirtualBase {
> public:
>
> static void* operator new( size_t size )
> {
> cout << "Allocation" << endl;
> ++g::nObjectsAllocated;
> return ::operator new( size );
> }
>
> static void operator delete( void* p )
> {
> cout << "Deallocation" << endl;
> --g::nObjectsAllocated;
> return ::operator delete( p );
> }
>
> Base(const VirtualBase::Initializer& i = Initializer()) : VirtualBase(i) {
> cout << "Base" << endl;
> #ifdef FAIL_CONSTRUCTOR
> throw runtime_error( "Oops, `Base::Base` failed." );
> #endif
> }
>
> ~Base() {
> cout << "~Base" << endl;
> }
>
> void Init() {
> cout << "Base::Init()" << endl;
> #ifdef FAIL_INIT
> throw runtime_error( "Oops, `Init` failed." );
> #endif
> }
> };
>
> int main()
> {
> int result = EXIT_FAILURE;
> try
> {
> Base* p = new Base; // calls Base::Init
> cout << endl;
> delete p;
> result = EXIT_SUCCESS;
> }
> catch( exception const& x )
> {
> cerr << "!" << x.what() << endl;
> }
>
> if( g::nObjectsAllocated > 0 )
> {
> cerr << "!Memory leak." << endl;
> }
> cout << "Finished." << endl;
> return result;
> }
> </code>
>
>
> As you can see the scheme, at least in its current incarnation, is not
> very exception-friendly: invoking UB, and leaking memory.
>
> Can it be made exception safe?
>
>
> Cheers & hth.,
>
> - Alf
>
Couldn't you use, in ~Initializer :
try {
if (p)
p->Init();
} catch(...) {
p->InitFailed();
throw;
}
And implement an InitFailed() virtual method that acts as the countrary
(not sure of the word to use there) of Init ?
So the code should have no more memory leaks.
Cheers,
Leo
[toc] | [prev] | [next] | [standalone]
| From | "Alf P. Steinbach /Usenet" <alf.p.steinbach+usenet@gmail.com> |
|---|---|
| Date | 2011-07-05 11:09 +0200 |
| Message-ID | <iuuk44$i31$1@dont-email.me> |
| In reply to | #7455 |
* Leo Equinox Gaspard, on 04.07.2011 23:59:
>
> Couldn't you [one] use, in ~Initializer :
>
> try {
> if (p)
> p->Init();
> } catch(...) {
> p->InitFailed();
> throw;
> }
No.
This could handle exceptions from Init in the sense of reacting to them, at the
cost of creating a zombie object (unusable object) when such an exception occurs.
But for this case it would still yield a memory leak, because this all happens
after the new-expression has been fully evaluated. When the exception occurs in
the constructor, the new-expression can clean up (and does). When the exception
occurs after the new-expression is fully evaluated, which is the case with the
call of Init, the new-expression is already out of the picture, in the past.
And it can't handle the case where the construction of the object that p is
pointing to, throws an exception.
For then, by the time the ~Initializer runs (at the end of the full-expression
that instantiates the object), the exception has propagated out of the
new-expression and, due to the new-expression's cleanup, the object that p
points to has been destroyed, hence, the call 'p->Init()' is now calling a
virtual function on a non-existent object, which is Undefined Behavior. With g++
the error is caught by the runtime, and you get a crash. With MSVC it's not
caught, and you get more subtle errors.
> And implement an InitFailed() virtual method that acts as the countrary (not
> sure of the word to use there) of Init ?
>
> So the code should have no more memory leaks.
Sorry, no, see above.
Cheers & hth.,
- Alf
--
blog at <url: http://alfps.wordpress.com>
[toc] | [prev] | [next] | [standalone]
| From | Leo Equinox Gaspard <ekinox1995@gmail.com> |
|---|---|
| Date | 2011-07-05 13:15 +0200 |
| Message-ID | <iuurn9$sck$1@dont-email.me> |
| In reply to | #7465 |
Le 05/07/2011 11:09, Alf P. Steinbach /Usenet a écrit :
> * Leo Equinox Gaspard, on 04.07.2011 23:59:
>>
>> Couldn't you [one] use, in ~Initializer :
>>
>> try {
>> if (p)
>> p->Init();
>> } catch(...) {
>> p->InitFailed();
>> throw;
>> }
>
> No.
>
> This could handle exceptions from Init in the sense of reacting to them,
> at the cost of creating a zombie object (unusable object) when such an
> exception occurs.
>
> But for this case it would still yield a memory leak, because this all
> happens after the new-expression has been fully evaluated. When the
> exception occurs in the constructor, the new-expression can clean up
> (and does). When the exception occurs after the new-expression is fully
> evaluated, which is the case with the call of Init, the new-expression
> is already out of the picture, in the past.
>
> And it can't handle the case where the construction of the object that p
> is pointing to, throws an exception.
>
> For then, by the time the ~Initializer runs (at the end of the
> full-expression that instantiates the object), the exception has
> propagated out of the new-expression and, due to the new-expression's
> cleanup, the object that p points to has been destroyed, hence, the call
> 'p->Init()' is now calling a virtual function on a non-existent object,
> which is Undefined Behavior. With g++ the error is caught by the
> runtime, and you get a crash. With MSVC it's not caught, and you get
> more subtle errors.
>
Oh. I thought the exception was in Init().
The run of the program I figured was :
=> new Derived()
=> Instantiation of an Initializer
=> Only do it, we don't want *any* exception in this constructor
=> End of new
=> ~Initializer()
=> Call to Init(), catching exceptions
=> Init() does the whole construction stuff, additionnally calling
Base's Init()
=> If any exception was caught, then clear the work done by Init()
using InitFailed()
=> InitFailed() should be exception-less and make the object ready to
be destructed.
=> Then rethrow the exception
=> End of ~Initializer
=> If any exception is thrown, then the object is not valid, and hence
should not be used, but immediately destructed. That's the default
behavior of the exception, which will make any scoped items to be deleted.
Of course, using a raw pointer will lead to memory leaks, but that's
always as it with them plus exceptions.
Where am I mistaking ?
Cheers,
Leo
>
>> And implement an InitFailed() virtual method that acts as the
>> countrary (not
>> sure of the word to use there) of Init ?
>>
>> So the code should have no more memory leaks.
>
> Sorry, no, see above.
>
>
> Cheers & hth.,
>
> - Alf
>
[toc] | [prev] | [next] | [standalone]
| From | "Alf P. Steinbach /Usenet" <alf.p.steinbach+usenet@gmail.com> |
|---|---|
| Date | 2011-07-05 14:51 +0200 |
| Message-ID | <iuv17c$uog$1@dont-email.me> |
| In reply to | #7467 |
* Leo Equinox Gaspard, on 05.07.2011 13:15:
> Le 05/07/2011 11:09, Alf P. Steinbach /Usenet a écrit :
>> * Leo Equinox Gaspard, on 04.07.2011 23:59:
>>>
>>> Couldn't you [one] use, in ~Initializer :
>>>
>>> try {
>>> if (p)
>>> p->Init();
>>> } catch(...) {
>>> p->InitFailed();
>>> throw;
>>> }
>>
>> No.
>>
>> This could handle exceptions from Init in the sense of reacting to them,
>> at the cost of creating a zombie object (unusable object) when such an
>> exception occurs.
>>
>> But for this case it would still yield a memory leak, because this all
>> happens after the new-expression has been fully evaluated. When the
>> exception occurs in the constructor, the new-expression can clean up
>> (and does). When the exception occurs after the new-expression is fully
>> evaluated, which is the case with the call of Init, the new-expression
>> is already out of the picture, in the past.
>>
>> And it can't handle the case where the construction of the object that p
>> is pointing to, throws an exception.
>>
>> For then, by the time the ~Initializer runs (at the end of the
>> full-expression that instantiates the object), the exception has
>> propagated out of the new-expression and, due to the new-expression's
>> cleanup, the object that p points to has been destroyed, hence, the call
>> 'p->Init()' is now calling a virtual function on a non-existent object,
>> which is Undefined Behavior. With g++ the error is caught by the
>> runtime, and you get a crash. With MSVC it's not caught, and you get
>> more subtle errors.
>>
>
> Oh. I thought the exception was in Init().
> The run of the program I figured was :
>
> => new Derived()
> => Instantiation of an Initializer
> => Only do it, we don't want *any* exception in this constructor
> => End of new
> => ~Initializer()
> => Call to Init(), catching exceptions
> => Init() does the whole construction stuff, additionnally calling
> Base's Init()
> => If any exception was caught, then clear the work done by Init()
> using InitFailed()
> => InitFailed() should be exception-less and make the object ready to
> be destructed.
> => Then rethrow the exception
> => End of ~Initializer
> => If any exception is thrown, then the object is not valid, and hence should
> not be used, but immediately destructed. That's the default behavior of the
> exception, which will make any scoped items to be deleted.
> Of course, using a raw pointer will lead to memory leaks, but that's always as
> it with them plus exceptions.
>
> Where am I mistaking ?
Instead of me explaining *again*, try out the given code. You can use compiler
switch "-D" to define a macro symbol.
Cheers & hth.,
- Alf
--
blog at <url: http://alfps.wordpress.com>
[toc] | [prev] | [next] | [standalone]
| From | Leo Equinox Gaspard <ekinox1995@gmail.com> |
|---|---|
| Date | 2011-07-05 19:55 +0200 |
| Message-ID | <iuvj59$s8m$1@dont-email.me> |
| In reply to | #7468 |
Le 05/07/2011 14:51, Alf P. Steinbach /Usenet a écrit :
> * Leo Equinox Gaspard, on 05.07.2011 13:15:
>> Oh. I thought the exception was in Init().
>> The run of the program I figured was :
>>
>> => new Derived()
>> => Instantiation of an Initializer
>> => Only do it, we don't want *any* exception in this constructor
>> => End of new
>> => ~Initializer()
>> => Call to Init(), catching exceptions
>> => Init() does the whole construction stuff, additionnally calling
>> Base's Init()
>> => If any exception was caught, then clear the work done by Init()
>> using InitFailed()
>> => InitFailed() should be exception-less and make the object ready to
>> be destructed.
>> => Then rethrow the exception
>> => End of ~Initializer
>> => If any exception is thrown, then the object is not valid, and hence
>> should
>> not be used, but immediately destructed. That's the default behavior
>> of the
>> exception, which will make any scoped items to be deleted.
>> Of course, using a raw pointer will lead to memory leaks, but that's
>> always as
>> it with them plus exceptions.
>>
>> Where am I mistaking ?
>
> Instead of me explaining *again*, try out the given code. You can use
> compiler switch "-D" to define a macro symbol.
>
> Cheers & hth.,
>
> - Alf
>
I *did* try it.
And, as soon as I change it to reflect what I mean - this means removing the
FAIL_CONSTRUCTOR case and adding a InitFailed() -, everything works OK. BTW
the NO_INIT_CALL looks useless to me, so I removed it.
And, for the -D switch, thanks, but I knew it.
Please also note that InitFailed doesn't has any use, but it could be
used in
case of raw pointer manipulation.
The code (you only have to play with FAIL_INIT) :
<code>
#include <iostream>
#include <stdexcept>
#include <cstdlib>
#include <memory>
using namespace std;
namespace g {
int nObjectsAllocated = 0;
} // namespace g
class VirtualBase {
protected:
class Initializer;
friend class Initializer;
class Initializer {
public:
Initializer() {
cout << "Initializer" << endl;
p = 0;
}
~Initializer() {
cout << "~Initializer" << endl;
try {
if (p)
p->Init(); // call the virtual function
} catch (...) {
p->InitFailed();
throw;
}
}
// might be private if VirtualBase is declared as friend...
void Register(VirtualBase* b) const {
p = b;
}
private:
mutable VirtualBase* p;
// private and not implemented
Initializer(const Initializer&);
void operator =(const Initializer&);
};
public:
VirtualBase(const Initializer& i) {
cout << "VirtualBase" << endl;
i.Register(this);
}
private:
virtual void Init() = 0;
// will be called immediately after the constructor
// of the most derived class
virtual void InitFailed() = 0;
// will be called if Init failed. Should leave the object
// in a destructable state.
};
// This is the actual hierarchy
class Base : public virtual VirtualBase {
public:
static void* operator new( size_t size )
{
cout << "Allocation" << endl;
++g::nObjectsAllocated;
return ::operator new( size );
}
static void operator delete( void* p )
{
cout << "Deallocation" << endl;
--g::nObjectsAllocated;
return ::operator delete( p );
}
Base(const VirtualBase::Initializer& i = Initializer()) :
VirtualBase(i) {
cout << "Base" << endl;
}
~Base() {
cout << "~Base" << endl;
}
void Init() {
cout << "Base::Init()" << endl;
#ifdef FAIL_INIT
throw runtime_error( "Oops, `Init` failed." );
#endif
}
void InitFailed() {
cout << "Base::InitFailed() - could do something if needed" <<
endl;
}
};
int main()
{
int result = EXIT_FAILURE;
try
{
auto_ptr<Base> p(new Base); // calls Base::Init.
// We *need* an auto_ptr to ensure that the item will be
// deleted in case of exception.
result = EXIT_SUCCESS;
}
catch( exception const& x )
{
cerr << "!" << x.what() << endl;
}
if( g::nObjectsAllocated > 0 )
{
cerr << "!Memory leak." << endl;
}
cout << "Finished." << endl;
return result;
}
</code>
So I must repeat my question : Where am I mistaking ?
Cheers,
Leo
[toc] | [prev] | [next] | [standalone]
| From | Anonymous <anonymous@no.net> |
|---|---|
| Date | 2011-07-05 20:46 +0200 |
| Message-ID | <iuvm6d$85n$2@speranza.aioe.org> |
| In reply to | #7482 |
Leo Equinox Gaspard ha scritto: > I *did* try it. > And, as soon as I change it to reflect what I mean - this means removing > the > FAIL_CONSTRUCTOR case and adding a InitFailed() -, everything works OK. BTW > the NO_INIT_CALL looks useless to me, so I removed it. > And, for the -D switch, thanks, but I knew it. > Please also note that InitFailed doesn't has any use, but it could be > used in > case of raw pointer manipulation. > The code (you only have to play with FAIL_INIT) : This is what I meant in my previous post. IMHO it should be ok.
[toc] | [prev] | [next] | [standalone]
| From | "Alf P. Steinbach /Usenet" <alf.p.steinbach+usenet@gmail.com> |
|---|---|
| Date | 2011-07-05 21:23 +0200 |
| Message-ID | <iuvo43$bb$1@dont-email.me> |
| In reply to | #7482 |
* Leo Equinox Gaspard, on 05.07.2011 19:55:
> <code>
> #include <iostream>
> #include <stdexcept>
> #include <cstdlib>
> #include <memory>
> using namespace std;
>
> namespace g {
> int nObjectsAllocated = 0;
> } // namespace g
>
> class VirtualBase {
> protected:
> class Initializer;
> friend class Initializer;
>
> class Initializer {
> public:
>
> Initializer() {
> cout << "Initializer" << endl;
> p = 0;
> }
>
> ~Initializer() {
> cout << "~Initializer" << endl;
> try {
> if (p)
> p->Init(); // call the virtual function
> } catch (...) {
> p->InitFailed();
> throw;
> }
> }
> // might be private if VirtualBase is declared as friend...
>
> void Register(VirtualBase* b) const {
> p = b;
> }
> private:
> mutable VirtualBase* p;
> // private and not implemented
> Initializer(const Initializer&);
> void operator =(const Initializer&);
> };
> public:
>
> VirtualBase(const Initializer& i) {
> cout << "VirtualBase" << endl;
> i.Register(this);
> }
> private:
> virtual void Init() = 0;
> // will be called immediately after the constructor
> // of the most derived class
>
> virtual void InitFailed() = 0;
> // will be called if Init failed. Should leave the object
> // in a destructable state.
> };
>
> // This is the actual hierarchy
>
> class Base : public virtual VirtualBase {
> public:
>
> static void* operator new( size_t size )
> {
> cout << "Allocation" << endl;
> ++g::nObjectsAllocated;
> return ::operator new( size );
> }
>
> static void operator delete( void* p )
> {
> cout << "Deallocation" << endl;
> --g::nObjectsAllocated;
> return ::operator delete( p );
> }
>
> Base(const VirtualBase::Initializer& i = Initializer()) : VirtualBase(i) {
> cout << "Base" << endl;
> }
>
> ~Base() {
> cout << "~Base" << endl;
> }
>
> void Init() {
> cout << "Base::Init()" << endl;
> #ifdef FAIL_INIT
> throw runtime_error( "Oops, `Init` failed." );
> #endif
> }
>
> void InitFailed() {
> cout << "Base::InitFailed() - could do something if needed" << endl;
> }
> };
>
> int main()
> {
> int result = EXIT_FAILURE;
> try
> {
> auto_ptr<Base> p(new Base); // calls Base::Init.
> // We *need* an auto_ptr to ensure that the item will be
> // deleted in case of exception.
> result = EXIT_SUCCESS;
> }
> catch( exception const& x )
> {
> cerr << "!" << x.what() << endl;
> }
>
> if( g::nObjectsAllocated > 0 )
> {
> cerr << "!Memory leak." << endl;
> }
> cout << "Finished." << endl;
> return result;
> }
> </code>
>
> So I must repeat my question : Where am I mistaking ?
Your code
* has Undefined Behavior if a constructor throws, and
* leaks memory without the auto_ptr
(while there's no leak for an ordinary single stage construction, because the
new-expression then cleans up).
The UB is critical, so this technique is only applicable for guaranteed no-throw
constructors, which except for default constructors are pretty rare beasts nowadays.
I don't know what you think you're mistaken about, sorry, but hopefully it's one
of the two points above?
Cheers & hth.,
- Alf
--
blog at <url: http://alfps.wordpress.com>
[toc] | [prev] | [next] | [standalone]
| From | Werner <werasm@gmail.com> |
|---|---|
| Date | 2011-07-06 00:14 -0700 |
| Message-ID | <fa4a7df4-f2c4-4fb4-802a-aeb29654bd42@c41g2000yqm.googlegroups.com> |
| In reply to | #7482 |
On Jul 5, 7:55 pm, Leo Equinox Gaspard <ekinox1...@gmail.com> wrote: > I *did* try it. > And, as soon as I change it to reflect what I mean - this means removing the > FAIL_CONSTRUCTOR case and adding a InitFailed() -, everything works OK. BTW > the NO_INIT_CALL looks useless to me, so I removed it. Unfortunately you cannot easily impose this requirement (that their constructors may not throw) on derived classes (the client). This makes this idiom very restrictive. Furthermore, throwing from within init, even when calling InitFailed still causes the leak (even when using an auto_ptr). Output using your addition (courtesy of codepad, my comments added): //Options used... //#define FAIL_CONSTRUCTOR #define FAIL_INIT //#define NO_INIT_CALL 1) Initializer //Parameter initialization from default 2) Allocation //new called. 3) VirtualBase //vbase constructor 4) Base //base constructor 5) ~Initializer //Parameter out of scope... 6) Base::Init() //Valid virtual call 7) Base::InitFailed() //ditto 8) !Oops, `Init` failed. //final catch, but // where is Deallocation? This despite the // fact that we used an autoptr (Oops, auto_ptr // was never initialized, as new never completed... 9) !Memory leak. //... is still there, apart from the //UB if we define FAIL_CONSTRUCTOR... 10) Finished. Kind regards, Werner
[toc] | [prev] | [next] | [standalone]
Page 1 of 2 [1] 2 Next page →
Back to top | Article view | comp.lang.c++
csiph-web