最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

c++ - Lifetime of temporary objects used while chaining overloaded member access operator-> - Stack Overflow

programmeradmin4浏览0评论

According to what I know, the result value of an overloaded operator->() can be any type T provided that T is either a pointer type or it is a class/struct which also exposes an overloaded operator->(). In effect the compiler has to potentially chain together multiple invocations of those overloaded member access operators, until a result value of a pointer type is obtained. What I'm interested in is whether the standard guarantees anything in what exact way this "chaining" should look like from the syntactic point of view. Consider the following example:

struct A {
  int data;
  A* operator->() { return this; }
};

struct B {
  A operator->() { return A(); }
};

struct C {
  B operator->() { return B(); }
};

int main() {
  C()->data = 1;
}

The part of particular interest here is the overloaded A* A::operator->() which returns a pointer to itself (return this). My first question is whether that's actually legal to do from the language standpoint. I'd imagine that it is, since it matches the requirement of an overloaded operator->() returning a pointer type. The only problem I see here is that there may be some conditions under which the returned A* pointer may become dangling. And therein lies the second question - whether the process of chaining multiple operator->() together by the compiler behind the scenes can result in that pointer becoming invalid. That could happen for instance if the expression C()->data = 1; would get "unrolled" into the following syntactically similar construct:

A* _internal_compiler_var = ((C().operator->()).operator->()).operator->(); // temporary A is constructed and immediately destroyed after obtaining the pointer value
_internal_compiler_var->data = 1; // Oops : dangling A* pointer

By contrast, the expression C()->data = 1; would be valid provided that compiler "unrolls" it into a one-liner like the following:

(((C().operator->()).operator->()).operator->())->data = 1; // OK : destruction of temporary A instance is performed after the assignment

So, to sum up what I'd like to know 2 things:

  1. whether an overloaded operator->() returning this pointer is valid, and if it is, if there are any potential problems that could come up because of this
  2. whether the standard has some implicit/explicit guarantees about (temporary) object lifetimes used inside the chain of overloaded operator->() (particulary if these temporary objects are destroyed after the enclosing expression or are destroyed somewhere in the middle while the resulting pointer value is being evaluated)

According to what I know, the result value of an overloaded operator->() can be any type T provided that T is either a pointer type or it is a class/struct which also exposes an overloaded operator->(). In effect the compiler has to potentially chain together multiple invocations of those overloaded member access operators, until a result value of a pointer type is obtained. What I'm interested in is whether the standard guarantees anything in what exact way this "chaining" should look like from the syntactic point of view. Consider the following example:

struct A {
  int data;
  A* operator->() { return this; }
};

struct B {
  A operator->() { return A(); }
};

struct C {
  B operator->() { return B(); }
};

int main() {
  C()->data = 1;
}

The part of particular interest here is the overloaded A* A::operator->() which returns a pointer to itself (return this). My first question is whether that's actually legal to do from the language standpoint. I'd imagine that it is, since it matches the requirement of an overloaded operator->() returning a pointer type. The only problem I see here is that there may be some conditions under which the returned A* pointer may become dangling. And therein lies the second question - whether the process of chaining multiple operator->() together by the compiler behind the scenes can result in that pointer becoming invalid. That could happen for instance if the expression C()->data = 1; would get "unrolled" into the following syntactically similar construct:

A* _internal_compiler_var = ((C().operator->()).operator->()).operator->(); // temporary A is constructed and immediately destroyed after obtaining the pointer value
_internal_compiler_var->data = 1; // Oops : dangling A* pointer

By contrast, the expression C()->data = 1; would be valid provided that compiler "unrolls" it into a one-liner like the following:

(((C().operator->()).operator->()).operator->())->data = 1; // OK : destruction of temporary A instance is performed after the assignment

So, to sum up what I'd like to know 2 things:

  1. whether an overloaded operator->() returning this pointer is valid, and if it is, if there are any potential problems that could come up because of this
  2. whether the standard has some implicit/explicit guarantees about (temporary) object lifetimes used inside the chain of overloaded operator->() (particulary if these temporary objects are destroyed after the enclosing expression or are destroyed somewhere in the middle while the resulting pointer value is being evaluated)
Share Improve this question asked 2 days ago Die4ToastDie4Toast 793 bronze badges New contributor Die4Toast is a new contributor to this site. Take care in asking for clarification, commenting, and answering. Check out our Code of Conduct. 11
  • 2 It doesn't make sense to return temporaries since the thing you modify is a temporary, and once it goes away so does your modification. The rule of thumb is temporaries live until the ; of the expression they were created in. – NathanOliver Commented 2 days ago
  • 1 1. yes it is valid. – Jarod42 Commented 2 days ago
  • 1 SO policy: one post - one question. If someone answers a first question, and someone answers a second question, you will not be able to accept both answers. – 3CxEZiVlQ Commented 2 days ago
  • Didn't know about that SO policy, but in any case the important part here is about the 2nd question. I'm fairly sure about the 1st one, but I wanted to confirm it either way. – Die4Toast Commented 2 days ago
  • 5 Temporary are destroyed at end of full expression (there are exception, but don't apply here). – Jarod42 Commented 2 days ago
 |  Show 6 more comments

1 Answer 1

Reset to default -1

Here is a main using the structures from the OP with live code

auto main() -> int {
    C()->data = 42;            // temporary C destroyed
    println("{}", C()->data);  // output 0, another C temporary destroyed

    A* _internal_compiler_var = ((C().operator->()).operator->()).operator->();
    _internal_compiler_var->data = 1; // undefined behavior
    println("{}", _internal_compiler_var->data); // output 1

    (((C().operator->()).operator->()).operator->())->data =
        84;  // temporary C destroyed
    println("{}", (((C().operator->()).operator->()).operator->())->data); // output 0

    return 0;
}

The undefined behaviors occur because C++ will retain the pointer to the temporary, but what it points to is destroyed.

发布评论

评论列表(0)

  1. 暂无评论