- Pragma once support - 4 Updates
- Lifetime, destructors, and sub-objects - 5 Updates
Juha Nieminen <nospam@thanks.invalid>: Dec 04 10:01AM > choosing include guards, as they are standard and supported everywhere. > But using both kind of defeats the purpose - it combines the > disadvantages of both methods. I like the approach that Objective-C(++) supports, which is to add a new preprocessor directive, #import, as an alternative to #include. The former works like the latter, except that it implicitly behaves as if the file being included has a #pragma once. Thus you don't need to write any sort of include guard in your headers (or any other files that you might be including). It might not sound like much, but it's actually pretty handy once you get used to it. I wish standard C++ introduced the same directive. |
David Brown <david.brown@hesbynett.no>: Dec 04 12:21PM +0100 On 04/12/18 11:01, Juha Nieminen wrote: > including). > It might not sound like much, but it's actually pretty handy once you > get used to it. I wish standard C++ introduced the same directive. One day, one happy day, we will have C++ modules that will handle this functionality. In the meantime, include guards are simple, portable, reliable, efficient, and supported by all compilers. I have never felt the need for #pragma once, nor #import (which some C and C++ tools support). |
Manfred <noname@add.invalid>: Dec 04 02:42PM +0100 On 12/4/2018 11:01 AM, Juha Nieminen wrote: > including). > It might not sound like much, but it's actually pretty handy once you > get used to it. I wish standard C++ introduced the same directive. #import has been used for a long time by Microsoft compilers to import COM type libraries. Given that, it is unlikely that it will ever make into the standard. Besides, I think the meaning that you suggest for #import is too much of a duplicate of the standard's #include, so it would not be worth a new standardized directive. |
Vir Campestris <vir.campestris@invalid.invalid>: Dec 03 09:10PM On 03/12/2018 14:11, Scott Lurndal wrote: > What operating system? Windows file operations are pathetically slow. Windows running in a VM! Andy |
Markus Moll <moll.markus@arcor.de>: Dec 04 01:53PM Hi I am suddenly questioning the correctness of a pattern that I've used for a while now and would like to gather opinions before I make my code unnecessarily illegible :-) Let's say I have the following class (struct for brevity): struct A : NonTrivialBaseClassWhoseContentsDoNotMatter { std::atomic<bool> flag; ~A(); }; and an object 'a' of type A. Now I have a thread that keeps a pointer to a, 'pa', and occasionally sets 'pa->flag'. Additionally, there's a function stop_thread() that will stop that thread. The thread is guaranteed not to access 'a' after a call to stop_thread. What I've been doing in the past was to call stop_thread() in A's destructor to make sure that 'a' will not be accessed after its destruction: A::~A() { stop_thread(); } However, a closer reading of the relevant sections in the current C++ draft led me to thinking that this can actually result in undefined behavior: Per 3.8, an object's lifetime ends when its destructor is called (if that constructor is non-trivial). Accessing any non-static members of an object whose lifetime has ended is undefined behavior. Now oddly enough, there seems to be a very easy but awkward way around this. The destructors of a's sub-objects will only be called once the body of A's destructor is left. Therefore, their lifetimes end only then. So to me, the following appears to have defined behavior: struct A : NonTrivialBaseClassWhoseContentsDoNotMatter { std::atomic<bool> flag; }; struct B : A { ~B(); }; B::~B() { stop_thread(); } Does the original code really exhibit undefined behavior? Does adding another derived class really help? Any insights are greatly appreciated Markus |
James Kuyper <jameskuyper@alumni.caltech.edu>: Dec 04 09:11AM -0500 On 12/4/18 08:53, Markus Moll wrote: > behavior: Per 3.8, an object's lifetime ends when its destructor is > called (if that constructor is non-trivial). Accessing any non-static > members of an object whose lifetime has ended is undefined behavior. The lifetime of an object of type A ends when ~A() starts. However, the lifetime of A's member objects only ends when they themselves get destroyed, which occurs at the end of ~A(). It's perfectly safe to access them from within ~A(). Note the word "finishes" in the following clause: "For an object with a non-trivial destructor, referring to any non-static member or base class of the object after the destructor finishes execution results in undefined behavior." (12.7p1). |
"Alf P. Steinbach" <alf.p.steinbach+usenet@gmail.com>: Dec 04 03:14PM +0100 On 04.12.2018 14:53, Markus Moll wrote: > Accessing any non-static > members of an object whose lifetime has ended is undefined behavior. No. The formal lifetime ends with start of the destructor execution (if any). But: C++17 §15.7/1 [quote] For an object with a non-trivial destructor, referring to any non-static member or base class of the object after the destructor finishes execution results in undefined behavior. [/quote] I.e. you're safe. :) It might feel a little bit weird that during the destructor execution there is an existing object whose lifetime has ended. But this is similar to how, during a constructor execution, there is an existing object whose lifetime has not yet started. It would be difficult to initialize an object if you couldn't do anything with it in the constructor body. After the constructor body finishes and the lifetime starts, the object might turn `const`, say. Similarly, its `const`-ness, if any, disappears when its lifetime ends and the destructor is invoked. Maybe think of it as a building. With a sufficiently simple view the building's lifetime as a proper building doesn't start until construction is finished (we disregard the common practice of doing some final things even after one has started using the building), and doesn't extend into the eventual destruction. Cheers & hth., - Alf |
Markus Moll <moll.markus@arcor.de>: Dec 04 02:44PM On Tue, 04 Dec 2018 15:14:40 +0100, Alf P. Steinbach wrote: > execution results in undefined behavior. > [/quote] > I.e. you're safe. :) Hm, I agree that accessing the sub-objects is safe (which is what I rely on in my revised code example). However, the problem seems to be accessing them through a pointer to A, as 3.8 states: [quote] (...) after the lifetime of an object has ended and before the storage which the object occupied is reused or released, any pointer that refers to the storage location where the object will be or was located may be used but only in limited ways. (...) The program has undefined behavior if: (...) - the pointer is used to access a non-static data member or call a non- static member function of the object [/quote] And yes, I think it would be safe to store a pointer to the atomic<bool> in the thread and use that pointer. However, I would prefer not to do that. Background: the thread in my example is a timer thread. Users can add or remove timer callbacks of type "void (*)(void*)". I follow the familiar pattern of using a static member with that signature which converts its argument to A* and then calls a corresponding non-static member function without further arguments. That call, in my opinion, would be undefined behavior. (And I find that both odd and surprising) Markus |
Markus Moll <moll.markus@arcor.de>: Dec 04 02:52PM Oh, alright, I just realized that 3.8 refers to 12.7 for objects under construction or destruction and that the restriction I quoted really only applies to "raw storage". That makes a lot more sense :-) Thanks everyone for your help Markus |
You received this digest because you're subscribed to updates for this group. You can change your settings on the group membership page. To unsubscribe from this group and stop receiving emails from it send an email to comp.lang.c+++unsubscribe@googlegroups.com. |
No comments:
Post a Comment