Date: Thu, 20 Jun 2019 18:59:13 -0400
On Thu, Jun 20, 2019 at 5:41 PM Matthew Woehlke via Std-Proposals <
std-proposals_at_[hidden]> wrote:
> I am struggling with the following code (grotesquely simplified):
>
> class a
> {
> public:
> static std::shared_ptr<a> create(int x)
> { return std::make_shared<a>(x); }
>
> protected:
> friend class std::shared_ptr<a>;
>
> a(int) {}
> };
>
> ...and I'm hardly alone: https://stackoverflow.com/questions/8147027.
>
> Most of the existing approached are either ugly (requiring introduction
> of "public" API that is made unusable outside of the class by using a
> private type in the parameter list), or essentially relies on various
> methods to circumvent access protection and can only access protected
> constructors.
>
> It occurred to me, there really ought to be a better way to fix this.
> (Also, smart pointer return type polymorphism, but that's a different
> discussion.) Poking around in libstdc++'s implementation of shared_ptr
> inspired me to try friending `std::shared_ptr<a>`, but that doesn't work
> either, since it is ultimately a helper function that constructs `a`. I
> then wondered if it would be completely unreasonable to specify an
> implementation detail that would permit this, although that's really
> still the wrong thing to do.
>
Some libraries, such as boost::iterator_facade, do take that approach.
https://www.boost.org/doc/libs/1_65_0/libs/iterator/doc/iterator_facade.html#iterator-core-access
The standard library *could* take that approach without an ABI break,
AFAIK; all it would need is someone to make the proposal.
> Then I had a better, though perhaps crazy idea... what about introducing
> a "friend scope"?
>
> static std::shared_ptr<a> create(int x)
> {
> friend
> {
> // Within this block, all access protection to `a` is suspended.
> ...
> }
> }
>
If you're allowed to rewrite the implementation of `create`, then you could
just pull the construction up to `create`'s level:
static std::shared_ptr<a> create(int x) {
return std::shared_ptr<a>(new a(x));
}
Or if you must use `make_shared` for its memory-footprint advantages, you
can do it via some insanity like:
https://godbolt.org/z/msXcjJ
static std::shared_ptr<a> create(int x) {
struct Wrapper {
int x;
operator a() const { return a(x); }
};
return std::make_shared<a>(Wrapper{x});
}
Regardless, I don't see how your idea about "friend scope" would help at
all. Your whole problem is that the access-to-protected-constructor happens
in a *non-friend* scope, buried deep inside `make_shared`. Adding a
"friend scope" somewhere inside A::create won't help a scope inside
`make_shared` access protected members.
HTH,
–Arthur
std-proposals_at_[hidden]> wrote:
> I am struggling with the following code (grotesquely simplified):
>
> class a
> {
> public:
> static std::shared_ptr<a> create(int x)
> { return std::make_shared<a>(x); }
>
> protected:
> friend class std::shared_ptr<a>;
>
> a(int) {}
> };
>
> ...and I'm hardly alone: https://stackoverflow.com/questions/8147027.
>
> Most of the existing approached are either ugly (requiring introduction
> of "public" API that is made unusable outside of the class by using a
> private type in the parameter list), or essentially relies on various
> methods to circumvent access protection and can only access protected
> constructors.
>
> It occurred to me, there really ought to be a better way to fix this.
> (Also, smart pointer return type polymorphism, but that's a different
> discussion.) Poking around in libstdc++'s implementation of shared_ptr
> inspired me to try friending `std::shared_ptr<a>`, but that doesn't work
> either, since it is ultimately a helper function that constructs `a`. I
> then wondered if it would be completely unreasonable to specify an
> implementation detail that would permit this, although that's really
> still the wrong thing to do.
>
Some libraries, such as boost::iterator_facade, do take that approach.
https://www.boost.org/doc/libs/1_65_0/libs/iterator/doc/iterator_facade.html#iterator-core-access
The standard library *could* take that approach without an ABI break,
AFAIK; all it would need is someone to make the proposal.
> Then I had a better, though perhaps crazy idea... what about introducing
> a "friend scope"?
>
> static std::shared_ptr<a> create(int x)
> {
> friend
> {
> // Within this block, all access protection to `a` is suspended.
> ...
> }
> }
>
If you're allowed to rewrite the implementation of `create`, then you could
just pull the construction up to `create`'s level:
static std::shared_ptr<a> create(int x) {
return std::shared_ptr<a>(new a(x));
}
Or if you must use `make_shared` for its memory-footprint advantages, you
can do it via some insanity like:
https://godbolt.org/z/msXcjJ
static std::shared_ptr<a> create(int x) {
struct Wrapper {
int x;
operator a() const { return a(x); }
};
return std::make_shared<a>(Wrapper{x});
}
Regardless, I don't see how your idea about "friend scope" would help at
all. Your whole problem is that the access-to-protected-constructor happens
in a *non-friend* scope, buried deep inside `make_shared`. Adding a
"friend scope" somewhere inside A::create won't help a scope inside
`make_shared` access protected members.
HTH,
–Arthur
Received on 2019-06-20 18:01:14