--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+unsubscribe@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/ec2e95cc-3ef0-435b-aaf9-13e982c4583e%40isocpp.org.
I don't quite get why simply calling the normal destructor for the object wouldn't achieve the optimization goals you pretend.
What you seem to be suggesting is that we can signal the complier that an object should be destroyed as soon as possible. This seems to be more of a quality of implementation issue on the compiler's side than something that needs to be standardized. And it doesn't need a different destructor.
> Am 24.08.2016 um 21:01 schrieb Ville Voutilainen <ville.vo...@gmail.com>:
>
>> Limiting the scope with extra braces or by putting the operations in a
>> subroutine also often isn't a desirable workaround. Note that compilers have
>
> ..because..?
It might sometimes be difficult to express the lifetimes as short as possible:
resource a; // begin minimum lifetime a
resource b; // begin minimum lifetime b
last_use (a); // end minimum lifetime a
long_task (b);
last_use (b); //end minimum lifetime b
// end current lifetime b
// end current lifetime a
What I'm saying is that while the notion you describe is useful, it effectively always needs to be explicit in some form at the point of desired destruction in a language that has pointers and references, like C++. What you'd do is, when the *user* is done with the object, they'd notify the compiler -- this would happen somewhat automatically with something like an explicit destructive move operation if we had them, but the idea is more general than move operations. Once such a destructive operation is used, the compiler would diagnose any in-scope uses after that point, and it would no longer run the destructor at the end of scope.
On 24 August 2016 at 21:51, <nicolas...@gmail.com> wrote:
> Hi all,
>
> I'd like to propose a new kind of destructor that gets called after a local
> object's last use, instead of at the end of its scope.
Define "last use".
> Limiting the scope with extra braces or by putting the operations in a
> subroutine also often isn't a desirable workaround. Note that compilers have
..because..?
The code would become littered with braces if you want to limit the scope of each object to its miminal range,
Fortunately we don't, because today's compilers do liveness analysis to optimize register allocation.
I don't quite get why simply calling the normal destructor for the object wouldn't achieve the optimization goals you pretend.
What you seem to be suggesting is that we can signal the complier that an object should be destroyed as soon as possible. This seems to be more of a quality of implementation issue on the compiler's side than something that needs to be standardized. And it doesn't need a different destructor.
Consider asking your compiler vendor for this as an enhancement, or consider implementing it yourself in an open source compiler. There just doesn't seem to be anything in this (interesting) idea that should be taken care of by the standard's committee, unless I have misunderstood it.
2016-08-24 15:51 GMT-03:00 <nicolas...@gmail.com>:
Hi all,I'd like to propose a new kind of destructor that gets called after a local object's last use, instead of at the end of its scope.The use of this would be to free resources held by the object sooner. This could be heap memory, file handles, mutex locks, etc. Releasing them as soon as we're done with the object that represents them would result in more efficient programs.Currently such optimization can only be achieved by explicitly releasing the resources, which is inconvenient, bug prone, and reduces readability. Limiting the scope with extra braces or by putting the operations in a subroutine also often isn't a desirable workaround. Note that compilers have been doing liveness analysis for register allocation and stack compaction for a very long time and we take these optimizations for granted. I'd like to see it get extended to heap memory and other resources as well.The syntax for this could be an annotation of the destructor declaration with a keyword, symbol, or attribute. For example, "auto ~Object();", "volatile ~Object();", "~~Object();", or "[[eager]] ~Object();".Thoughts?- Nicolas
--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposal...@isocpp.org.
On Wed, Aug 24, 2016 at 11:51 AM, <nicolas...@gmail.com> wrote:Hi all,I'd like to propose a new kind of destructor that gets called after a local object's last use, instead of at the end of its scope.The use of this would be to free resources held by the object sooner. This could be heap memory, file handles, mutex locks, etc. Releasing them as soon as we're done with the object that represents them would result in more efficient programs.Currently such optimization can only be achieved by explicitly releasing the resources, which is inconvenient, bug prone, and reduces readability. Limiting the scope with extra braces or by putting the operations in a subroutine also often isn't a desirable workaround. Note that compilers have been doing liveness analysis for register allocation and stack compaction for a very long time and we take these optimizations for granted. I'd like to see it get extended to heap memory and other resources as well.The syntax for this could be an annotation of the destructor declaration with a keyword, symbol, or attribute. For example, "auto ~Object();", "volatile ~Object();", "~~Object();", or "[[eager]] ~Object();".Thoughts?While I would like C++ to eventually get a way to do the equivalent of this in some form (more generally, I want destructive move, which is related to this even though it may not be immediately obvious),
I do not think that the appropriate place to notate the premature destruction is on the destructor declaration. Instead, I believe it needs to be explicit at the usage-site. This is primarily because we are in a language where side-effects matter.
For example, consider one of the kinds of types that you explicitly called out: locks. A lock declaration has no actual knowledge of the memory that it is effectively guarding, so there is no way to *actually* know when the object is truly done with. Indeed, often times the object isn't even touched directly *at all* once constructed. Prematurely destroying such an object would be horrible. This issue is true of basically any object that has a relationship with another object, such as iterators and their associated container.
Even with that, you might think that it's okay to annotate types that contain no logical relationships to objects that it does not own. For instance, something that just contains an int or a unique_ptr to some object with no relationships, etc. The problem is, even in these cases, there is nothing stopping some *other* object from storing a pointer or reference to an instance of your automatically-prematurely-destructible-type. If someone *does* hold such a reference and your annotated type is "no longer used", then the object that refers to it will now have a dangling reference. Point being, your annotation cannot be at the type level.
On Wednesday, August 24, 2016 at 3:01:34 PM UTC-4, Ville Voutilainen wrote:On 24 August 2016 at 21:51, <nicolas...@gmail.com> wrote:
> Hi all,
>
> I'd like to propose a new kind of destructor that gets called after a local
> object's last use, instead of at the end of its scope.
Define "last use".The last use according to liveness analysis.
int main()
{
resource a;
funcA(&a);
funcB();
funcC();
funcD();
}
On Wednesday, August 24, 2016 at 3:19:24 PM UTC-4, Matt Calabrese wrote:While I would like C++ to eventually get a way to do the equivalent of this in some form (more generally, I want destructive move, which is related to this even though it may not be immediately obvious),Yes, a destructive assignment could count as a last use of its previous value and call the destructor that I'm proposing. I'd also like it to be called after the last use as a source argument too though.
I do not think that the appropriate place to notate the premature destruction is on the destructor declaration. Instead, I believe it needs to be explicit at the usage-site. This is primarily because we are in a language where side-effects matter.I wouldn't call it premature destruction. Eager instead of today's lazy destruction at the end of the scope, seems like a better description to me.
Anyway, for the use cases that I envision, the annotation at destructor declaration is appropriate. For example say I need a huge matrix to temporarily store some intermediate results. I could use "double matrix[10000][10000];" and rely on the compiler's optimizations to use the memory for other purposes as soon as I'm done with this matrix. But it's simply not going to fit on the stack. So instead I'll use an object with a pointer to heap memory. But now it only gets freed when it goes out of scope. So I want to make it clear to the compiler that I'd like it to be destroyed eagerly, and not just for this one but for every instance. It's a property of the class itself that I want it's destructor to be called eagerly. If I don't want that behavior I can simply use an equivalent class without eager destructor.
For example, consider one of the kinds of types that you explicitly called out: locks. A lock declaration has no actual knowledge of the memory that it is effectively guarding, so there is no way to *actually* know when the object is truly done with. Indeed, often times the object isn't even touched directly *at all* once constructed. Prematurely destroying such an object would be horrible. This issue is true of basically any object that has a relationship with another object, such as iterators and their associated container.The solution here is to make the lock and the memory that it's guarding part of the same object.
This issue is not very different from still having a reference to a local variable after it has gone out of scope. C++ is inherently unsafe, and you simply need to know what you're doing. With lazy destruction, don't use any references to the object after it has gone out of scope. With eager destruction, don't use any references to the object after its last use. A compiler warning could help prevent people from shooting themselves in the foot in both cases.
On Wednesday, August 24, 2016 at 3:19:24 PM UTC-4, Matt Calabrese wrote:On Wed, Aug 24, 2016 at 11:51 AM, <nicolas...@gmail.com> wrote:Hi all,I'd like to propose a new kind of destructor that gets called after a local object's last use, instead of at the end of its scope.The use of this would be to free resources held by the object sooner. This could be heap memory, file handles, mutex locks, etc. Releasing them as soon as we're done with the object that represents them would result in more efficient programs.Currently such optimization can only be achieved by explicitly releasing the resources, which is inconvenient, bug prone, and reduces readability. Limiting the scope with extra braces or by putting the operations in a subroutine also often isn't a desirable workaround. Note that compilers have been doing liveness analysis for register allocation and stack compaction for a very long time and we take these optimizations for granted. I'd like to see it get extended to heap memory and other resources as well.The syntax for this could be an annotation of the destructor declaration with a keyword, symbol, or attribute. For example, "auto ~Object();", "volatile ~Object();", "~~Object();", or "[[eager]] ~Object();".Thoughts?While I would like C++ to eventually get a way to do the equivalent of this in some form (more generally, I want destructive move, which is related to this even though it may not be immediately obvious),Yes, a destructive assignment could count as a last use of its previous value and call the destructor that I'm proposing. I'd also like it to be called after the last use as a source argument too though.I do not think that the appropriate place to notate the premature destruction is on the destructor declaration. Instead, I believe it needs to be explicit at the usage-site. This is primarily because we are in a language where side-effects matter.I wouldn't call it premature destruction. Eager instead of today's lazy destruction at the end of the scope, seems like a better description to me.
Anyway, for the use cases that I envision, the annotation at destructor declaration is appropriate. For example say I need a huge matrix to temporarily store some intermediate results. I could use "double matrix[10000][10000];" and rely on the compiler's optimizations to use the memory for other purposes as soon as I'm done with this matrix. But it's simply not going to fit on the stack. So instead I'll use an object with a pointer to heap memory. But now it only gets freed when it goes out of scope.
auto heap_allocation = std::make_unique<T>(...);
//use the object
heap_allocation.reset();
Object(std::move(auto_val));
So I want to make it clear to the compiler that I'd like it to be destroyed eagerly, and not just for this one but for every instance. It's a property of the class itself that I want it's destructor to be called eagerly. If I don't want that behavior I can simply use an equivalent class without eager destructor.
Even with that, you might think that it's okay to annotate types that contain no logical relationships to objects that it does not own. For instance, something that just contains an int or a unique_ptr to some object with no relationships, etc. The problem is, even in these cases, there is nothing stopping some *other* object from storing a pointer or reference to an instance of your automatically-prematurely-destructible-type. If someone *does* hold such a reference and your annotated type is "no longer used", then the object that refers to it will now have a dangling reference. Point being, your annotation cannot be at the type level.This issue is not very different from still having a reference to a local variable after it has gone out of scope. C++ is inherently unsafe, and you simply need to know what you're doing. With lazy destruction, don't use any references to the object after it has gone out of scope. With eager destruction, don't use any references to the object after its last use. A compiler warning could help prevent people from shooting themselves in the foot in both cases.
--
You received this message because you are subscribed to a topic in the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this topic, visit https://groups.google.com/a/isocpp.org/d/topic/std-proposals/IUxA4cx8yHc/unsubscribe.
To unsubscribe from this group and all its topics, send an email to std-proposal...@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/CAA7YVg3dJT3LY_L5Eg%3DZNgfqqyOcX9P%3DJVv_pXTEx6xxsoGOdg%40mail.gmail.com.
template<class T>
void finish(T&& x)
{
auto y{std::move(x)};
}
void f()
{
MyExpensiveResource r;
/* code */
finish(r); // 'r' is not needed anymore.
/* code */
}
Would it not be convenient enough to define a helper function? Would the following work?
template<class T>
void finish(T&& x)
{
auto y{std::move(x)};
}
void f()
{
MyExpensiveResource r;
/* code */
finish(r); // 'r' is not needed anymore.
/* code */
}
On Wednesday, August 24, 2016 at 3:16:52 PM UTC-4, Bernd Lörwald wrote:
> Am 24.08.2016 um 21:01 schrieb Ville Voutilainen <ville.vo...@gmail.com>:
>
>> Limiting the scope with extra braces or by putting the operations in a
>> subroutine also often isn't a desirable workaround. Note that compilers have
>
> ..because..?
It might sometimes be difficult to express the lifetimes as short as possible:
resource a; // begin minimum lifetime a
resource b; // begin minimum lifetime b
last_use (a); // end minimum lifetime a
long_task (b);
last_use (b); //end minimum lifetime b
// end current lifetime b
// end current lifetime a
And how do you know that `b` isn't using `a`?
That's the point of Ville's "Define 'last use'." question. You cannot just look at code and know that `a` is finished being used when the locally visible code stops using it.
Destruction of automatic objects in the reverse order of their construction is a guarantee that the standard should not arbitrarily discard.
--
You received this message because you are subscribed to a topic in the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this topic, visit https://groups.google.com/a/isocpp.org/d/topic/std-proposals/IUxA4cx8yHc/unsubscribe.
To unsubscribe from this group and all its topics, send an email to std-proposal...@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/a54b7cf2-6c83-48d5-aeb6-4c179cef1520%40isocpp.org.
On quarta-feira, 24 de agosto de 2016 11:51:01 PDT nicolas...@gmail.com
wrote:
> The use of this would be to free resources held by the object sooner. This
> could be heap memory, file handles, mutex locks, etc. Releasing them as
> soon as we're done with the object that represents them would result in
> more efficient programs.
Locks are the perfect counter-example to what you're proposing. The compiler
simply cannot tell what the "last use" is:
std::lock_guard lock(mutex);
i = 1;
Where was the last use: before or after the assignment to i?
--
Thiago Macieira - thiago (AT) macieira.info - thiago (AT) kde.org
Software Architect - Intel Open Source Technology Center
--
You received this message because you are subscribed to a topic in the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this topic, visit https://groups.google.com/a/isocpp.org/d/topic/std-proposals/IUxA4cx8yHc/unsubscribe.
To unsubscribe from this group and all its topics, send an email to std-proposal...@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/5044039.aVzB6kA9Ui%40tjmaciei-mobl1.
On 24 August 2016 at 15:16, <nicolas...@gmail.com> wrote:The code would become littered with braces if you want to limit the scope of each object to its miminal range,I don't understand why you would want to do that. Could you elaborate?
And if the objects are truly independent and have no observable effect, can't a compiler do that under the as-if rule?
Also, if you wish to have a lifetime shorter than a scope for an object of type T, you can just use std::optional<T>.
Fortunately we don't, because today's compilers do liveness analysis to optimize register allocation.One of the huge benefits to C++ is that destruction happens at well defined times. This would break that.
--Nevin ":-)" Liber <mailto:ne...@eviloverlord.com> +1-847-691-1404
--
You received this message because you are subscribed to a topic in the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this topic, visit https://groups.google.com/a/isocpp.org/d/topic/std-proposals/IUxA4cx8yHc/unsubscribe.
To unsubscribe from this group and all its topics, send an email to std-proposal...@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/CAGg_6%2BM3M43y_8jTg4mXce1sz7UQ5kzSkOVqL3F_nYEe-ogzNA%40mail.gmail.com.
On Wed, Aug 24, 2016 at 4:22 PM Nevin Liber <ne...@eviloverlord.com> wrote:On 24 August 2016 at 15:16, <nicolas...@gmail.com> wrote:The code would become littered with braces if you want to limit the scope of each object to its miminal range,I don't understand why you would want to do that. Could you elaborate?Certainly. I wrote down three practical use cases here.And if the objects are truly independent and have no observable effect, can't a compiler do that under the as-if rule?The problem is that the objects I'm using do have side-effects, so the compiler won't attempt to shorten their lifespan. But in my use cases the side effects are "self-contained", i.e. you could treat them as scalars and destruct them right after their last use.Also, if you wish to have a lifetime shorter than a scope for an object of type T, you can just use std::optional<T>.How would that work, without requiring manual actions to destruct it?Fortunately we don't, because today's compilers do liveness analysis to optimize register allocation.One of the huge benefits to C++ is that destruction happens at well defined times. This would break that.Yes, in many cases it will be preferable to keep the well defined behavior of implicitly calling the destructor at the end of the variable's scope. The opt-in feature I'm proposing should be used sparingly and cautiously. But when used by a well written framework it should be foolproof for the users of the framework, and offer them significant benefits.
----Nevin ":-)" Liber <mailto:ne...@eviloverlord.com> +1-847-691-1404
You received this message because you are subscribed to a topic in the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this topic, visit https://groups.google.com/a/isocpp.org/d/topic/std-proposals/IUxA4cx8yHc/unsubscribe.
To unsubscribe from this group and all its topics, send an email to std-proposals+unsubscribe@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/CAGg_6%2BM3M43y_8jTg4mXce1sz7UQ5kzSkOVqL3F_nYEe-ogzNA%40mail.gmail.com.
--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+unsubscribe@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/CAE7XuEVobd1QW2jJYW-hGTLTPgQxRJM-QkL9jNovOkhaaoXQEw%40mail.gmail.com.
Why is it such a terrible imposition to require manual interaction to enforce this behavior? I don't buy your argument that it is similar to register allocation; that's a QOI not a standards issue.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/CAMD6iD_8zsoqfYdUsY%2BQe_QNvzFsH4c9uUjNYzw5BRFiLQRE%2BQ%40mail.gmail.com.
If so then what would this extra destructor do that
T(std::move(aT));
doesn't do today?
/MF
--
You received this message because you are subscribed to a topic in the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this topic, visit https://groups.google.com/a/isocpp.org/d/topic/std-proposals/IUxA4cx8yHc/unsubscribe.
To unsubscribe from this group and all its topics, send an email to std-proposals+unsubscribe@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/20160824214337.GA18752%40noemi.
On Wed, Aug 24, 2016 at 2:17 PM, <nicolas...@gmail.com> wrote:On Wednesday, August 24, 2016 at 3:19:24 PM UTC-4, Matt Calabrese wrote:While I would like C++ to eventually get a way to do the equivalent of this in some form (more generally, I want destructive move, which is related to this even though it may not be immediately obvious),Yes, a destructive assignment could count as a last use of its previous value and call the destructor that I'm proposing. I'd also like it to be called after the last use as a source argument too though.Again, the problem is that "last use" isn't something that can actually be determined in any meaningful sense. For this to be usable, the *user* needs to notate their last use by explicitly calling some kind of special destructive operation that doesn't currently exist in the language (a hypothetical destructive move operation would be one of those special kinds of operations). I and others have given you examples of why what you want cannot be done truly automatically in a language like C++.
On Wed, Aug 24, 2016 at 2:17 PM, <nicolas...@gmail.com> wrote:I do not think that the appropriate place to notate the premature destruction is on the destructor declaration. Instead, I believe it needs to be explicit at the usage-site. This is primarily because we are in a language where side-effects matter.I wouldn't call it premature destruction. Eager instead of today's lazy destruction at the end of the scope, seems like a better description to me.There is no way in practice to implicitly know when an object is okay to be disposed of without the user providing such information.On Wed, Aug 24, 2016 at 2:17 PM, <nicolas...@gmail.com> wrote:Anyway, for the use cases that I envision, the annotation at destructor declaration is appropriate. For example say I need a huge matrix to temporarily store some intermediate results. I could use "double matrix[10000][10000];" and rely on the compiler's optimizations to use the memory for other purposes as soon as I'm done with this matrix. But it's simply not going to fit on the stack. So instead I'll use an object with a pointer to heap memory. But now it only gets freed when it goes out of scope. So I want to make it clear to the compiler that I'd like it to be destroyed eagerly, and not just for this one but for every instance. It's a property of the class itself that I want it's destructor to be called eagerly. If I don't want that behavior I can simply use an equivalent class without eager destructor.Before again going into the subtleties of why annotation strictly at the type level won't work, just think about what you've said -- the user would have to use an equivalent type without eager destruction if they didn't want this behavior. So now you have two types that do exactly the same thing but one with an "eager" destructor?
As I'll reiterate, the user effectively needs to somehow notate at the usage point where they are done with the object, but even if you don't get that argument, certainly your desire for two equivalent types should hint to you that someone would at least want to put the notation at the point of declaration of the *object* that they want "eagerly" disposed of, instead of having it be a part of the type. This removes the need for multiple types that you describe.Anyway, apart from that, another simple example of where this breaks down: you have your matrix type that you've described and some accessor taking a row index and a column index. This function returns a reference to an element of that matrix. Someone uses the function to access an element. When is your matrix ready for disposal? Nobody is 'touching" your matrix type immediately after you call that function, but a reference to one of its elements still exists. Strictly speaking, the matrix could be "eagerly" destroyed as soon as the accessor function returns, before the element is even truly accessed through the reference. Even if you delay your disposal until the end of the full expression, what happens if the reference is stored and accessed in the next statement? This is not at all an unreasonable scenario. Do you expect to track such references? If so, why -- how would the compiler know which references to track. Is the compiler expected to know that the reference which is returned corresponds to something that is owned by the object that was accessed as opposed to just a reference to some other, longer-lived double? What happens if, instead of storing the reference, the result of the access is directly passed along as an argument to another function? Would you just give up on eager destruction there?
Ultimately, the user has the information that is needed and it is trivial for them to specify, just as there is no problem with people writing things like std::move today.On Wed, Aug 24, 2016 at 2:17 PM, <nicolas...@gmail.com> wrote:For example, consider one of the kinds of types that you explicitly called out: locks. A lock declaration has no actual knowledge of the memory that it is effectively guarding, so there is no way to *actually* know when the object is truly done with. Indeed, often times the object isn't even touched directly *at all* once constructed. Prematurely destroying such an object would be horrible. This issue is true of basically any object that has a relationship with another object, such as iterators and their associated container.The solution here is to make the lock and the memory that it's guarding part of the same object.This is not always feasible nor does it actually solve the problem.On Wed, Aug 24, 2016 at 2:17 PM, <nicolas...@gmail.com> wrote:This issue is not very different from still having a reference to a local variable after it has gone out of scope. C++ is inherently unsafe, and you simply need to know what you're doing. With lazy destruction, don't use any references to the object after it has gone out of scope. With eager destruction, don't use any references to the object after its last use. A compiler warning could help prevent people from shooting themselves in the foot in both cases.I'd argue that they are very different and it has everything to do with your notion of "last use." This is not something that can actually be detected, and if you rely on a naive notion of "use" being someone directly touching the object, not even through a reference, then IMO it's practically useless and extremely error prone.
I'm sorry if I sound overly negative here, but the problems of this are already well understood. For any kind of early destruction in a language like C++, users would need to be explicit, and that's not bad in this case. I genuinely do believe that something similar to this is worthwhile, but your particular approach is unrealistic (I think specifically that destructive-move would solve a lot of problems with the language and library as it is today).
Em quarta-feira, 24 de agosto de 2016, às 13:16:30 PDT,
nicolas...@gmail.com escreveu:
> > Define "last use".
>
> The last use according to liveness analysis.
Let's make it stricter: the last time the object is formally ODR-used.
But then, what happens to lifetime extension via references?
resource a;
const resource &r = a;
When does a get destroyed?
On Fri, Aug 26, 2016 at 1:16 PM, Ren Industries <renind...@gmail.com> wrote:Why is it such a terrible imposition to require manual interaction to enforce this behavior? I don't buy your argument that it is similar to register allocation; that's a QOI not a standards issue.Consider this simple statement:x = a * b * c;Assume they're all scalars, and a, b, and c are no longer used after this statement. That means liveness analysis will make their registers available for other variables, without you having to worry about it. But if instead these variables were huge matrices and you had to manually free their storage to minimize memory consumption, then it would look something like this:Matrix t = a * b;a.~Matrix();b.~Matrix();x = t * c;t.~Matrix();c.~Matrix();
I hope the above example illustrates that this is very similar to register allocation, and it's a standards issue that we don't get this behavior today for non-scalar types.
Thanks all for the initial feedback! It's clear to me now that I haven't defined "last use" very well, and giving it an accurate definition based on my limited knowledge of compiler lingo is going to be tricky.So let me take a step back and describe what real-world issues I'm trying to solve. The first example is one where I'd like the heap memory managed by an object to get freed at the same point where a scalar variable's register would be made available for a different variable:
T t = ...
auto tpl = forward_as_tuple(t);
//do something with tpl.
<some_syntax> TypeName varName;
Matrix a = ...
for(loop)
{
Matrix b = a * whatever;
//Do stuff with b, without using a
}
Nicol: Absolutely. Eager destruction should be a strictly opt-in feature. And it would be better if anyone could opt into it, with any objects they nominate, rather than only those whose interfaces they can edit.
So +1 for the idea of this being an attribute not of a destructor itself, but rather of the declaration - as a hint to the optimiser that it can proceed as-if no one (or no one in immediate scope) has stashed a handle to the instance and use that to deallocate early, iff it sees value.
That said, I wonder how likely this is, given that by my understanding, we're far into GC territory. But as a non-binding hint to the compiler that wouldn't affect semantics of well-formed programs, maybe it's not that outlandish at all... we have several of those already.
Hmm. At first, I didn't like the idea of it being merely a hint to the optimizer. When a destructor gets called is an observable side-effect for any code where we would care to use such a thing. So it should be something you could rely on.
After thinking about it for a bit however, making it a hint does solve a number of issues.
That said, I wonder how likely this is, given that by my understanding, we're far into GC territory. But as a non-binding hint to the compiler that wouldn't affect semantics of well-formed programs, maybe it's not that outlandish at all... we have several of those already.
But it would affect the semantics of well-formed programs. See my tuple example above; if `T t` had been annotated with the attribute, then it would make use of `tpl` without the use of `t` undefined behavior. That's part of the promise you make when you use [[eager-destruct]] or whatever we call it.
It's no different from [[noreturn]] in that regard; if such a function ever actually returns, you provoke UB.
<some_syntax> varName;
what if <some_syntax> is explicitly applied on the intended last use of varName after which any reference to varName will be treated as compile time error
in summary make <some_syntax> end the scope of varName even before the end of the current scope
--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposal...@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/CACGiwhF_PGSqbfROx5tvxDBj0nsTpao3kOKqSGzpsKmTDwTjTQ%40mail.gmail.com.
<some_syntax> varName;what if <some_syntax> is explicitly applied on the intended last use of varName after which any reference to varName will be treated as compile time error
in summary make <some_syntax> end the scope of varName even before the end of the current scope
On Wednesday, August 24, 2016 at 5:17:26 PM UTC-4, nicolas...@gmail.com wrote:On Wednesday, August 24, 2016 at 3:19:24 PM UTC-4, Matt Calabrese wrote:On Wed, Aug 24, 2016 at 11:51 AM, <nicolas...@gmail.com> wrote:Hi all,I'd like to propose a new kind of destructor that gets called after a local object's last use, instead of at the end of its scope.The use of this would be to free resources held by the object sooner. This could be heap memory, file handles, mutex locks, etc. Releasing them as soon as we're done with the object that represents them would result in more efficient programs.Currently such optimization can only be achieved by explicitly releasing the resources, which is inconvenient, bug prone, and reduces readability. Limiting the scope with extra braces or by putting the operations in a subroutine also often isn't a desirable workaround. Note that compilers have been doing liveness analysis for register allocation and stack compaction for a very long time and we take these optimizations for granted. I'd like to see it get extended to heap memory and other resources as well.The syntax for this could be an annotation of the destructor declaration with a keyword, symbol, or attribute. For example, "auto ~Object();", "volatile ~Object();", "~~Object();", or "[[eager]] ~Object();".Thoughts?While I would like C++ to eventually get a way to do the equivalent of this in some form (more generally, I want destructive move, which is related to this even though it may not be immediately obvious),Yes, a destructive assignment could count as a last use of its previous value and call the destructor that I'm proposing. I'd also like it to be called after the last use as a source argument too though.I do not think that the appropriate place to notate the premature destruction is on the destructor declaration. Instead, I believe it needs to be explicit at the usage-site. This is primarily because we are in a language where side-effects matter.I wouldn't call it premature destruction. Eager instead of today's lazy destruction at the end of the scope, seems like a better description to me.
Playing word games doesn't change the fact that the standard makes it clear that destruction of an automatic object happens in a single, well-defined place. Your proposal wants to *transparently* change that location based on something in the type of that object.
The ability to execute the destructor and simultaneously prevent the compiler from doing so is something that can be discussed and debated. But as Matt said, it would be explicit at the point of use, not implicitly built into the type. The code that wants to change the default behavior, which is the code that gains certain responsibilities by doing so, is the code using the object, not the code defining it.
Putting it in the type is simply the wrong place for it. It's the code that needs the optimization who should be responsible for invoking this behavior.Anyway, for the use cases that I envision, the annotation at destructor declaration is appropriate. For example say I need a huge matrix to temporarily store some intermediate results. I could use "double matrix[10000][10000];" and rely on the compiler's optimizations to use the memory for other purposes as soon as I'm done with this matrix. But it's simply not going to fit on the stack. So instead I'll use an object with a pointer to heap memory. But now it only gets freed when it goes out of scope.
No, it gets freed when you want it to be freed. Observe:
auto heap_allocation = std::make_unique<T>(...);
//use the object
heap_allocation.reset();
See? it's been freed after the last use.
This way, you don't have to play games of wondering exactly when the allocation is destroyed. It's destroyed when you destroy it.
You can explicitly reset smart pointers; you can explicitly close streams. And for moveable types that don't offer such features, Magnus gave a great answer:
Object(std::move(auto_val));
That works for pretty much any moveable `Object` type that represents a resource.
The point in time when an automatic variable is destroyed should not be a mystery. It should never be in question when an automatic variable is no longer a legitimate object.
So I want to make it clear to the compiler that I'd like it to be destroyed eagerly, and not just for this one but for every instance. It's a property of the class itself that I want it's destructor to be called eagerly. If I don't want that behavior I can simply use an equivalent class without eager destructor.
You've provided a very good reason not to do this at all. We do not want to encourage the proliferation of types that differ only by the presence or absence of "eager destruction". We don't want `eager_unique_ptr` or `eager_ifstream` or `eager_vector` or whatever.
Even with that, you might think that it's okay to annotate types that contain no logical relationships to objects that it does not own. For instance, something that just contains an int or a unique_ptr to some object with no relationships, etc. The problem is, even in these cases, there is nothing stopping some *other* object from storing a pointer or reference to an instance of your automatically-prematurely-destructible-type. If someone *does* hold such a reference and your annotated type is "no longer used", then the object that refers to it will now have a dangling reference. Point being, your annotation cannot be at the type level.This issue is not very different from still having a reference to a local variable after it has gone out of scope. C++ is inherently unsafe, and you simply need to know what you're doing. With lazy destruction, don't use any references to the object after it has gone out of scope. With eager destruction, don't use any references to the object after its last use. A compiler warning could help prevent people from shooting themselves in the foot in both cases.
What would trigger such a warning? Passing a variable of such a type as a reference to someone else? Because that's all the compiler can know: that a reference or pointer was passed to some function. It has no idea if that function retains that value or not, or when it is expected to relinquish it.
You'd need serious static analysis to avoid a litany of false positives.
That in no way demonstrates it is similar to register allocation, which is, again, a QOI issue.
It is not necessarily the case that a compiler need do that, and I can think of at least a few counter examples. For example, there are projects that output C++ to javascript or java, which have no notion of register allocation as you point to it. These "machines" are perfectly valid C++ targets, yet have absolutely no notion of what you are talking about.
Indeed, C++ supports a huge variety of applications. More than you know of, it seems.
To unsubscribe from this group and all its topics, send an email to std-proposal...@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/CAGg_6%2BM3M43y_8jTg4mXce1sz7UQ5kzSkOVqL3F_nYEe-ogzNA%40mail.gmail.com.
--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposal...@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/CAE7XuEVobd1QW2jJYW-hGTLTPgQxRJM-QkL9jNovOkhaaoXQEw%40mail.gmail.com.
--
You received this message because you are subscribed to a topic in the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this topic, visit https://groups.google.com/a/isocpp.org/d/topic/std-proposals/IUxA4cx8yHc/unsubscribe.
To unsubscribe from this group and all its topics, send an email to std-proposal...@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
On Wed, Aug 24, 2016 at 6:05 PM, Nicol Bolas <jmck...@gmail.com> wrote:On Wednesday, August 24, 2016 at 5:17:26 PM UTC-4, nicolas...@gmail.com wrote:On Wednesday, August 24, 2016 at 3:19:24 PM UTC-4, Matt Calabrese wrote:On Wed, Aug 24, 2016 at 11:51 AM, <nicolas...@gmail.com> wrote:Hi all,I'd like to propose a new kind of destructor that gets called after a local object's last use, instead of at the end of its scope.The use of this would be to free resources held by the object sooner. This could be heap memory, file handles, mutex locks, etc. Releasing them as soon as we're done with the object that represents them would result in more efficient programs.Currently such optimization can only be achieved by explicitly releasing the resources, which is inconvenient, bug prone, and reduces readability. Limiting the scope with extra braces or by putting the operations in a subroutine also often isn't a desirable workaround. Note that compilers have been doing liveness analysis for register allocation and stack compaction for a very long time and we take these optimizations for granted. I'd like to see it get extended to heap memory and other resources as well.The syntax for this could be an annotation of the destructor declaration with a keyword, symbol, or attribute. For example, "auto ~Object();", "volatile ~Object();", "~~Object();", or "[[eager]] ~Object();".Thoughts?While I would like C++ to eventually get a way to do the equivalent of this in some form (more generally, I want destructive move, which is related to this even though it may not be immediately obvious),Yes, a destructive assignment could count as a last use of its previous value and call the destructor that I'm proposing. I'd also like it to be called after the last use as a source argument too though.I do not think that the appropriate place to notate the premature destruction is on the destructor declaration. Instead, I believe it needs to be explicit at the usage-site. This is primarily because we are in a language where side-effects matter.I wouldn't call it premature destruction. Eager instead of today's lazy destruction at the end of the scope, seems like a better description to me.
Playing word games doesn't change the fact that the standard makes it clear that destruction of an automatic object happens in a single, well-defined place. Your proposal wants to *transparently* change that location based on something in the type of that object.It's not a word game. Premature anything is a bad thing and we shouldn't present this feature to users that way. Lazy vs. eager is more neutral. Other suggestions welcome.
You can explicitly reset smart pointers; you can explicitly close streams. And for moveable types that don't offer such features, Magnus gave a great answer:
Object(std::move(auto_val));
That works for pretty much any moveable `Object` type that represents a resource.
The point in time when an automatic variable is destroyed should not be a mystery. It should never be in question when an automatic variable is no longer a legitimate object.Why? We don't question whether a register is still in use or not, don't we?
So I want to make it clear to the compiler that I'd like it to be destroyed eagerly, and not just for this one but for every instance. It's a property of the class itself that I want it's destructor to be called eagerly. If I don't want that behavior I can simply use an equivalent class without eager destructor.
You've provided a very good reason not to do this at all. We do not want to encourage the proliferation of types that differ only by the presence or absence of "eager destruction". We don't want `eager_unique_ptr` or `eager_ifstream` or `eager_vector` or whatever.The way I envision this to be used you won't need such a prefix at all. It would just be Matrix, Int, or Streamer, like in my examples. The framework they're part of is responsible of making sure they're self-contained objects and under normal circumstances can't be used incorrectly.
T t = ...
auto tpl = forward_as_tuple(t);
//do something with tpl.
const auto &str = std::string(...);
//use str
const auto &str = std::string(...).c_str();
//use str
On 2016-08-24 14:51, nicolas...@gmail.com wrote:
> I'd like to propose a new kind of destructor that gets called after a local
> object's last use, instead of at the end of its scope.
> [...]
General follow-up... I'm not sure I'm in favor of this proposal in
general, but I don't see it as feasible to make it a type property. You
are almost inevitably going to have cases where the feature needs to be
controlled on a per-variable basis, and having to produce duplicate
types (and convert between them!) is not practical, not to mention that
you may want to use it on types outside your control. (You mention
matrices a lot; well, what if I'm using Eigen matrices and want this?)
And then it occurred to me... we have the `register` keyword just
sitting there. And you keep referring to register allocation of
scalars... it seems that the old meaning of `register` is almost exactly
what you want.
It may also make sense to make it UB to take and retain a reference to
any `register` variable, including references to members of the same. If
we *really* wanted to be safe, we could make `register` a qualifier
(i.e. allow its use on function parameters) and make it *ill-formed* to
take a (non-`register`) reference to a `register` variable.
On Friday, August 26, 2016 at 12:21:42 PM UTC-7, Nicolas Capens wrote:On Fri, Aug 26, 2016 at 1:16 PM, Ren Industries <renind...@gmail.com> wrote:Why is it such a terrible imposition to require manual interaction to enforce this behavior? I don't buy your argument that it is similar to register allocation; that's a QOI not a standards issue.Consider this simple statement:x = a * b * c;Assume they're all scalars, and a, b, and c are no longer used after this statement. That means liveness analysis will make their registers available for other variables, without you having to worry about it. But if instead these variables were huge matrices and you had to manually free their storage to minimize memory consumption, then it would look something like this:Matrix t = a * b;a.~Matrix();b.~Matrix();x = t * c;t.~Matrix();c.~Matrix();Well, it would look like this:x = a * b * c;a.clear();b.clear();c.clear();Or, if you wanted to be more C++11'ish about it,x = std::move(a) * std::move(b) * std::move(c);to indicate that you're really done using those named variables. It's really not a big deal.
I hope the above example illustrates that this is very similar to register allocation, and it's a standards issue that we don't get this behavior today for non-scalar types.You've already realized that we do get this behavior today for trivially destructible types, and in general we get this behavior for every type by the as-if rule (for types whose destructors have no observable side effects). Your one remaining concern is that we currently don't get this behavior for destructors whose side effects are observable.
Here we seem to have two cases:(Case 1) Destructors whose side effects are "important", such as ~std::lock_guard<std::mutex>.(Case 2) Destructors whose side effects are "unimportant", such as ~std::string.
Let's consider what might happen if we allowed std::string's destructor to run "eagerly." Some people in this thread have already shown one problem:std::string x = "hello";char *y = &x[0];use(x);// run x's destructor now?std::cout << y << std::endl;Here, we are done with the variable x, but we aren't done with the object referenced by x — the object that would be destroyed.
But okay, let's postulate that std::string is a bad candidate for eager destruction because it allows the user to get a pointer into its innards. After all, std::string also unsafely allows us to do things like this —std::string x = "hello";char *y = &x[0];x = "world";std::cout << y << std::endl; // UBSo let's promise not to do anything stupid with pointers, okay? Now are we foolproof?Well. The side-effect of ~std::string that is observable under the current rules is its memory deallocation, which results in some calls to operator delete[] and so on. What could go wrong if we allow that deallocation to happen eagerly, given that we've promised not to do anything stupid with pointers?std::string x = "hello";if (true) {std::lock_guard<std::mutex> lk(mtx);use(x);// run x's destructor now?}Here we are performing our last operation on x while holding a mutex lock. Our last operation ("use(x)") is presumably something cheap and fast, or else we would try to do it outside the lock. But the destructor call, by definition, frees memory, which means it's not cheap. (Allocating memory is slower yet, but deallocating memory isn't always super cheap either.) And in fact it probably tries to take a global lock itself, under some circumstances, so now we're holding our original lock on "mtx" through a very slow and possibly even blocking memory-deallocation operation. Well, that's not good!
Okay, so we shouldn't use eager destruction for types that expose references to their internals; and we shouldn't use eager destruction for types whose destructors might block on synchronization or I/O (because those types might get eagerly destructed under a mutex); and it goes without saying that we shouldn't use eager destruction for types whose destructors might throw; and we shouldn't use eager destruction for types whose destructors are trivial (because we get eager destruction for free under the as-if rule in those cases).So what cases have we got left at this point where eager destruction is possible? Certainly your motivating use-case of "Matrix" isn't a candidate, both because of the internal-reference problem and because of the memory-deallocation-is-a-blocking-operation problem. My above (Case 1) and (Case 2) turn out to have collapsed into a single case:(Case 1) Destructors whose side effects might be important to somebody.
my $.02,–Arthur
On 2016-08-29 14:46, Nicol Bolas wrote:
> This problem is fundamentally identical to the temporary lifetime extension
> problem. Namely, that this works:
>
> const auto &str = std::string(...);
> //use str
>
> While this does not:
>
> const auto &str = std::string(...).c_str();
> //use str
I think having explicit temporary types might help here:
std::string::c_str() -> char const* register; // declaration
// error: taking reference of register-qualified type.
const auto &str = std::string(...).c_str();
Obviously, it is too late to change std::string this way, but it would
help us write better API's going forward.
Specifically:
> The compiler does not and *cannot* know, in all cases, whether a
> return value of a function contains a pointer/reference to an
> argument or an element of an argument.
...it would be a way to solve this.
On 2016-08-29 14:52, Nicol Bolas wrote:
> So now you want `const virtual register` qualifiers on variables? Do we
> *really* need that?
Well... *yes*. For exactly the reason given above. Making it a qualifier
makes it possible to actually *solve* some of these problems, at least
by making them explicitly ill-formed rather than just subtly broken.
On Thursday, August 25, 2016 at 5:24:39 PM UTC-4, Nicolas Capens wrote:Thanks all for the initial feedback! It's clear to me now that I haven't defined "last use" very well, and giving it an accurate definition based on my limited knowledge of compiler lingo is going to be tricky.So let me take a step back and describe what real-world issues I'm trying to solve. The first example is one where I'd like the heap memory managed by an object to get freed at the same point where a scalar variable's register would be made available for a different variable:
I don't think anyone misunderstands what your intended use cases are. But the fact is that things do not get used the way they are intended at all times.
Consider a basic bit of template programming:
T t = ...
auto tpl = forward_as_tuple(t);
//do something with tpl.
This code, today, works for any type `T` which can be constructed with whatever ... was. Your feature would now make this code suspect. It would only work for `T`s that do not use "eager" destruction. So if someone wanted to instantiate this template with your `Matrix` type, this code breaks.
Normal amounts of compiler analysis cannot fix that. After all, the compiler doesn't (necessarily) know what's going on in `forward_as_tuple`. All the compiler sees is that you're passing a reference in, and you're getting at tuple<T&> out. How could the compiler know that `tpl` contains a reference to `t`? It can't; not without doing serious usage analysis.
And that's for a simple case; `forward_as_tuple` is a template function, so the compiler has its implementation. It could have been a non-template function that wasn't inlined. At which point, it requires whole program analysis to know for certain that the return value contains a reference to a function parameter.
Remember: this is the confounding problem with temporary lifetime extension, when passing temporaries through functions.
Who's fault is this failure? Is it the fault of the person who wrote this template code without knowing that such a feature was going to be introduced? No, of course not. It has to be the fault of the person who passed `Matrix` to this template. But how could they know that `Matrix`'s eager destructor would cause the template to break?
Thus, the blame must fall on the feature itself.
Your post talks about the utility of this feature with "scalar types". But what you're really talking about is how you use the type. The type is safe so long as you treat it as a pure value: you never get a pointer/reference to it or of its contents. But note where this distinction is made. It's not made in the type's declaration (you can get references to `int`, after all). It's made at the point of use.
The problem, as Matt has said, is that your feature works at the type level. To make this feature work, it has to happen at the usage level. The person declaring that variable must explicitly state that they will use the type as a pure value. That they won't get pointers/references to itself or its contents.
The right way to handle this is to make it a function of the object's declaration:
<some_syntax> TypeName varName;
That would cause the compiler to destroy the variable after its last named use.
This allows template programming to actually work. If a template function needed to use eager destruction, then the template would express that in the declaration of an automatic variable. And therefore, the person writing that template function would know that they cannot do tricks on the variable like `forward_as_tuple`.
Also, there still need to be clarifications on what "last use" means. Consider:
Matrix a = ...
for(loop)
{
Matrix b = a * whatever;
//Do stuff with b, without using a
}
When does `a` get destroyed? I guess it would have to be after the loop, right? Otherwise, the compiler would have to advance the loop prematurely to see if `a` needs to be destroyed.
How does this work with conditional branching? Or God help you, with `goto`?
The other advantage to making eager destruction a function of how the object is declared is that it can be used with anything. We couldn't declare that any currently existing standard library types use eager destruction. And yet, there's no reason why we couldn't use it with most such types, so long as the uses of those objects don't violate the "never get pointers/references" rule.
If eager destruction is as important as you believe it is, then I shouldn't have to write a wrapper type around `vector` or `deque` or whatever just to get that behavior. Users of `vector` have just as much right to benefit from said optimization as users of your class do (so long as they follow the rules).
On 2016-08-24 14:51, nicolas...@gmail.com wrote:
> I'd like to propose a new kind of destructor that gets called after a local
> object's last use, instead of at the end of its scope.
> [...]
General follow-up... I'm not sure I'm in favor of this proposal in
general, but I don't see it as feasible to make it a type property. You
are almost inevitably going to have cases where the feature needs to be
controlled on a per-variable basis, and having to produce duplicate
types (and convert between them!) is not practical, not to mention that
you may want to use it on types outside your control. (You mention
matrices a lot; well, what if I'm using Eigen matrices and want this?)
And then it occurred to me... we have the `register` keyword just
sitting there. And you keep referring to register allocation of
scalars... it seems that the old meaning of `register` is almost exactly
what you want.
It may also make sense to make it UB to take and retain a reference to
any `register` variable, including references to members of the same. If
we *really* wanted to be safe, we could make `register` a qualifier
(i.e. allow its use on function parameters) and make it *ill-formed* to
take a (non-`register`) reference to a `register` variable. This way,
you could still pass such a variable by reference to a function, but the
function would not be allowed to retain that reference. (This obviously
implies that no global variable may be `register` qualified, and the
presence of a `register` qualified member implies that the containing
object may only be used as a `register` variable itself.) This would
require much more extensive API modification for the feature to be
widely used, but the added safety might be worth it.
In fact, I can see some other potential (though probably breaking) uses
for such a feature. For example, std::string::data() could return `char*
register` instead of plain `char*` to indicate to callers that they may
not retain the returned pointer. (Maybe this could be a feature for STL2...)
--
Matthew
--
You received this message because you are subscribed to a topic in the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this topic, visit https://groups.google.com/a/isocpp.org/d/topic/std-proposals/IUxA4cx8yHc/unsubscribe.
To unsubscribe from this group and all its topics, send an email to std-proposal...@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/57C44F09.4010901%40gmail.com.
On Sat, Aug 27, 2016 at 4:54 PM Nicol Bolas <jmck...@gmail.com> wrote:The other advantage to making eager destruction a function of how the object is declared is that it can be used with anything. We couldn't declare that any currently existing standard library types use eager destruction. And yet, there's no reason why we couldn't use it with most such types, so long as the uses of those objects don't violate the "never get pointers/references" rule.
If eager destruction is as important as you believe it is, then I shouldn't have to write a wrapper type around `vector` or `deque` or whatever just to get that behavior. Users of `vector` have just as much right to benefit from said optimization as users of your class do (so long as they follow the rules).That's indeed an attractive argument. I'm still a bit wary though that using a new feature like eager destruction with a legacy class could more easily lead to bugs. For example std::string too easily allows access to its internal storage.
[[eager-destruct]] std::string str = std::to_string(53.4f);
std::cout << str;
//done with `str`.
Also, if eager destructions isn't fully deterministic but implementation dependent, then something that might work fine on one compiler can cause issues on another.
This is somewhat less likely when a class was designed with eager destruction in mind in the first place.
Anyway, I'm not against providing the user with more options. Perhaps we can simply allow to specify eager destruction both at the class level and per declaration.
On Sat, Aug 27, 2016 at 11:10 AM Nicol Bolas <jmck...@gmail.com> wrote:
By the way, one other potential way this could be solved is to not allow storing referenced to classes with eager destruction.
That is, they can be function arguments, but they can't be member variables or global variables. The above code would then simply not compile and you'd get a clear error message about it.
The problem, as Matt has said, is that your feature works at the type level. To make this feature work, it has to happen at the usage level. The person declaring that variable must explicitly state that they will use the type as a pure value. That they won't get pointers/references to itself or its contents.
The right way to handle this is to make it a function of the object's declaration:
<some_syntax> TypeName varName;If this can be part of a typedef (like const), then that seems reasonable to me.
Note that I really wouldn't want to annotate the use of a Reactor type at each variable declaration.
I just think it would end up being used as part of a typedef, all the time. For "legitimate" cases where you need to indicate it on a per declaration level, it seems like it would be a badly designed class and framework, and using it correctly would be much more painful than using a class which always uses eager destruction and helps you avoid the pitfalls by design.
Also such code would be hard to refactor.
It seems mentally less demanding to me to just know that for certain types you always have to avoid certain scenarios.
That would cause the compiler to destroy the variable after its last named use.
This allows template programming to actually work. If a template function needed to use eager destruction, then the template would express that in the declaration of an automatic variable. And therefore, the person writing that template function would know that they cannot do tricks on the variable like `forward_as_tuple`.
Also, there still need to be clarifications on what "last use" means. Consider:
Matrix a = ...
for(loop)
{
Matrix b = a * whatever;
//Do stuff with b, without using a
}
When does `a` get destroyed? I guess it would have to be after the loop, right? Otherwise, the compiler would have to advance the loop prematurely to see if `a` needs to be destroyed.
How does this work with conditional branching? Or God help you, with `goto`?Good points. For the Streamer use case it would make sense to be aggressive about calling the destructor at the earliest possible, while for the Matrix case it might be reasonable to wait until the next branch merge point.
Thanks all for the initial feedback! It's clear to me now that I haven't defined "last use" very well, and giving it an accurate definition based on my limited knowledge of compiler lingo is going to be tricky.So let me take a step back and describe what real-world issues I'm trying to solve. The first example is one where I'd like the heap memory managed by an object to get freed at the same point where a scalar variable's register would be made available for a different variable:
class Matrix{public:Matrix(size_t rows, size_t columns);auto ~Matrix(); // Eager destructor...private:double *m;};{Matrix a(10'000, 10'000) = ...;Matrix b(10'000, 10'000) = ...;Matrix c(10'000, 10'000) = ...;a += b; // Last use of b, destruct before the next linea += c;}The alternative of using explicit destruction is really inconvenient:{Matrix a(10'000, 10'000) = ...;Matrix b(10'000, 10'000) = ...;Matrix c(10'000, 10'000) = ...;a += b;b.~Matrix();a += c;}As is reducing the scope:{Matrix a(10'000, 10'000) = ...;{Matrix b(10'000, 10'000) = ...;a += b;}
Matrix c(10'000, 10'000) = ...;a += c;}
{
Matrix a(10'000, 10'000) = ...;
optional<Matrix> b(in_place, 10'000, 10'000) = ...;
Matrix c(10'000, 10'000) = ...;
a += *b;
b = nullopt;
a += c;
}
(Reposting from correct account; sorry for the noise.)
You can do better than that using the exchange-empty idiom:
a += *std::exchange(b, nullopt);
However I believe that OP considers this also too clumsy (look for discussion of std::move above).
One thing I haven't seen mentioned yet in this discussion is that allocation and deallocation is already elidable per [expr.new]/10. This means that e.g. the destructor of std::string is not an observable operation.
I 100% disagree that manual freeing is not acceptable; as Arthur indicated, it is not particularly hard to work around.I agree with him; it is not a big deal.
Furthermore, it's absolutely absurd you'd say "too bad" to working and quite important projects that do in fact use C++; that is not "implementation side"; it is a perfectly valid backend that does not in any way do "register allocation".We can picture machines that do not use registers; why exactly should we add an implementation detail to C++, which is defined in terms of an abstract machine NOT a real one?
I do not doubt the practical use for such a feature; in fact, I could use such a feature quite often, and manually annotate my code to make such features possible, even going so far as to occasionally use wrappers to make it easier. Just because I can see a feature is useful does not mean it deserves to be part of the standard, or that we should disrupt currently working code to implement it, especially when a library solution is so obvious.
I understand what you mean by register allocation, but I again do not find it to be in any way similar beyond superficially, and once again, register allocation is part of implementation not the standard. It is completely possible for a compiler to do no register allocation at all; the reason it can is because of the "as if" rule. You want non-trivial destructors to be called out of order? Not allowed by the "as if" rule, for very good reasons. Trying to hack it in using implementation details is a bad idea.
Guess that's "too bad" for you, then.
On Monday, August 29, 2016 at 11:22:42 AM UTC-4, Nicolas Capens wrote:On Wed, Aug 24, 2016 at 6:05 PM, Nicol Bolas <jmck...@gmail.com> wrote:On Wednesday, August 24, 2016 at 5:17:26 PM UTC-4, nicolas...@gmail.com wrote:On Wednesday, August 24, 2016 at 3:19:24 PM UTC-4, Matt Calabrese wrote:On Wed, Aug 24, 2016 at 11:51 AM, <nicolas...@gmail.com> wrote:Hi all,I'd like to propose a new kind of destructor that gets called after a local object's last use, instead of at the end of its scope.The use of this would be to free resources held by the object sooner. This could be heap memory, file handles, mutex locks, etc. Releasing them as soon as we're done with the object that represents them would result in more efficient programs.Currently such optimization can only be achieved by explicitly releasing the resources, which is inconvenient, bug prone, and reduces readability. Limiting the scope with extra braces or by putting the operations in a subroutine also often isn't a desirable workaround. Note that compilers have been doing liveness analysis for register allocation and stack compaction for a very long time and we take these optimizations for granted. I'd like to see it get extended to heap memory and other resources as well.The syntax for this could be an annotation of the destructor declaration with a keyword, symbol, or attribute. For example, "auto ~Object();", "volatile ~Object();", "~~Object();", or "[[eager]] ~Object();".Thoughts?While I would like C++ to eventually get a way to do the equivalent of this in some form (more generally, I want destructive move, which is related to this even though it may not be immediately obvious),Yes, a destructive assignment could count as a last use of its previous value and call the destructor that I'm proposing. I'd also like it to be called after the last use as a source argument too though.I do not think that the appropriate place to notate the premature destruction is on the destructor declaration. Instead, I believe it needs to be explicit at the usage-site. This is primarily because we are in a language where side-effects matter.I wouldn't call it premature destruction. Eager instead of today's lazy destruction at the end of the scope, seems like a better description to me.
Playing word games doesn't change the fact that the standard makes it clear that destruction of an automatic object happens in a single, well-defined place. Your proposal wants to *transparently* change that location based on something in the type of that object.It's not a word game. Premature anything is a bad thing and we shouldn't present this feature to users that way. Lazy vs. eager is more neutral. Other suggestions welcome.
The term "playing word games" refers to making an argument about the name of something in order to make it seem more palatable. So you seem to be admitting to playing word games while simultaneously denying it.
You can explicitly reset smart pointers; you can explicitly close streams. And for moveable types that don't offer such features, Magnus gave a great answer:
Object(std::move(auto_val));
That works for pretty much any moveable `Object` type that represents a resource.
The point in time when an automatic variable is destroyed should not be a mystery. It should never be in question when an automatic variable is no longer a legitimate object.Why? We don't question whether a register is still in use or not, don't we?
I don't know why you keep bringing up registers. Object lifetimes are not like registers in any way, shape, or form. Registers are an implementation detail of a particular machine/compiler. Object lifetimes are specified by the standard.
This is a false analogy; please stop using it.
So I want to make it clear to the compiler that I'd like it to be destroyed eagerly, and not just for this one but for every instance. It's a property of the class itself that I want it's destructor to be called eagerly. If I don't want that behavior I can simply use an equivalent class without eager destructor.
You've provided a very good reason not to do this at all. We do not want to encourage the proliferation of types that differ only by the presence or absence of "eager destruction". We don't want `eager_unique_ptr` or `eager_ifstream` or `eager_vector` or whatever.The way I envision this to be used you won't need such a prefix at all. It would just be Matrix, Int, or Streamer, like in my examples. The framework they're part of is responsible of making sure they're self-contained objects and under normal circumstances can't be used incorrectly.
Your interface cannot stop people from using the object "incorrectly". Consider the template example I used elsewhere:
T t = ...
auto tpl = forward_as_tuple(t);
//do something with tpl.This is perfectly valid and reasonable code; there is no reason to consider this code to not be "under normal circumstances" for C++. Template code like this already exists, and it will work with any type `T` which can be constructed.
There is nothing you as the creator of the type `T` to make it so that `T`'s interface will prevent someone from doing this.
And there are no tricks the compiler can play to be absolutely certain that `tpl` doesn't contain a reference to `t`. Yes, as I admitted elsewhere, `forward_as_tuple` is inline, so the compiler could in theory figure it out in that case. But it cannot do so in the general case. To the compiler, a non-inline function is a complete black box; it knows the inputs and it knows the outputs. But it doesn't know what happens in the middle.
This problem is fundamentally identical to the temporary lifetime extension problem. Namely, that this works:
const auto &str = std::string(...);
//use str
While this does not:
const auto &str = std::string(...).c_str();
//use str
Why doesn't this work? Because the compiler cannot know that `c_str` returns a pointer/reference into something controlled by the temporary. Therefore, the compiler must assume that the function's return value is independent of the lifetime of the parameter/this. This could have worked if `c_str` were a data member. But because it's a member function, it will not extend the lifetime of the temporary.
Your problem is the exact same thing, just for named variables instead of temporaries. The compiler does not and cannot know, in all cases, whether a return value of a function contains a pointer/reference to an argument or an element of an argument.
Your framework cannot prevent "incorrect" use of the type. So your proposal is flawed.
--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+unsubscribe@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/CAE7XuEU1qjr6dLqzKUyAQSGVLuCwFMykr4G_6GFP77AGEwL-xQ%40mail.gmail.com.
On Mon, Aug 29, 2016 at 2:46 PM Nicol Bolas <jmck...@gmail.com> wrote:
With that out of the way, note that it's only one of the two options. Either we use eager destruction as a hint, so it does become an implementation detail. You seem to be liking that idea. But that goes against your "the point in time when an automatic variable is destroyed should not be a mystery" mantra.
The way I envision this to be used you won't need such a prefix at all. It would just be Matrix, Int, or Streamer, like in my examples. The framework they're part of is responsible of making sure they're self-contained objects and under normal circumstances can't be used incorrectly.
Your interface cannot stop people from using the object "incorrectly". Consider the template example I used elsewhere:
T t = ...
auto tpl = forward_as_tuple(t);
//do something with tpl.This is perfectly valid and reasonable code; there is no reason to consider this code to not be "under normal circumstances" for C++. Template code like this already exists, and it will work with any type `T` which can be constructed.
There is nothing you as the creator of the type `T` to make it so that `T`'s interface will prevent someone from doing this.First of all, there's no law that dictates that legacy code under new conditions still has to have the same semantics. Something as simple as a + b would for a C developer be nothing other than an addition of scalars, while in C++ it can mean absolutely anything due to operator overloading. That can be badly abused, but it can also be a very powerful abstraction. So I don't think not breaking your template example is a strict necessity.
Secondly, we could explore whether we can make it a compilation error to use this code with an eagerly destructed type/variable.
Your framework cannot prevent "incorrect" use of the type. So your proposal is flawed.Many C++ features can be used incorrectly with nothing to prevent you from it. That hasn't stopped them from making it into the standard.