digitalmars.D - PIMPL Idiom in D
- Walter Bright (47/47) May 21 2018 In C, the way to do PIMPL is to write just a struct declaration:
- Steven Schveighoffer (10/74) May 21 2018 Don't you mean a nonpointer here? Otherwise, there are 2 indirections
- Walter Bright (6/80) May 21 2018 That's right. To avoid that, you'll need to organize the code for separa...
- Steven Schveighoffer (25/40) May 22 2018 But link time optimization should be OK, because the idea is that you
- Walter Bright (4/5) May 25 2018 That is correct. But pragmatically, I wanted the public interface to be ...
In C, the way to do PIMPL is to write just a struct declaration: === s.h === struct S; === s.c === #include "s.h" struct S { ... }; === t.c === #include "s.h" struct T { S* pimpl; }; And the users of T cannot access anything in S. The straightforward equivalent in D is: === s.di === struct S; === s.d === struct S { ... } === t.d === import s; struct T { S* pimpl; }; But if you don't want to use .di files, such as when compiling all the files together with one command, then things get a bit awkward. D doesn't like: === s.d === public struct S; private struct S { ... } Making a public alias to a private struct doesn't work, as the struct's members are still accessible. I finally found a way: === s.d === public struct Spimpl { private S* pimpl; private alias pimpl this; } private struct S { ... } === t.d === import s; struct T { Spimpl* s; } and the contents of S are inaccessible to T, while type safety is maintained. There are no extra indirections - the alias this takes care of that. s.d can use s to directly access S's contents, but t.d cannot. https://github.com/dlang/dmd/blob/master/src/dmd/func.d#L229 https://github.com/dlang/dmd/blob/master/src/dmd/dinterpret.d#L151 C: http://wiki.c2.com/?PimplIdiom C++: http://en.cppreference.com/w/cpp/language/pimpl
May 21 2018
On 5/21/18 5:23 PM, Walter Bright wrote:In C, the way to do PIMPL is to write just a struct declaration: === s.h === struct S; === s.c === #include "s.h" struct S { ... }; === t.c === #include "s.h" struct T { S* pimpl; }; And the users of T cannot access anything in S. The straightforward equivalent in D is: === s.di === struct S; === s.d === struct S { ... } === t.d === import s; struct T { S* pimpl; }; But if you don't want to use .di files, such as when compiling all the files together with one command, then things get a bit awkward. D doesn't like: === s.d === public struct S; private struct S { ... } Making a public alias to a private struct doesn't work, as the struct's members are still accessible. I finally found a way: === s.d === public struct Spimpl { private S* pimpl; private alias pimpl this; } private struct S { ... } === t.d === import s; struct T { Spimpl* s;Don't you mean a nonpointer here? Otherwise, there are 2 indirections needed.} and the contents of S are inaccessible to T, while type safety is maintained. There are no extra indirections - the alias this takes care of that. s.d can use s to directly access S's contents, but t.d cannot. https://github.com/dlang/dmd/blob/master/src/dmd/func.d#L229 https://github.com/dlang/dmd/blob/master/src/dmd/dinterpret.d#L151 C: http://wiki.c2.com/?PimplIdiom C++: http://en.cppreference.com/w/cpp/language/pimplWill this really work? I mean, if the compiler can see the implementation of S, even though it's private, can't it inline things and screw with the binary compatibility? I thought that was the main benefit of pimpl, to create a barrier which you know is going to separate the user from the implementation. -Steve
May 21 2018
On 5/21/2018 2:41 PM, Steven Schveighoffer wrote:On 5/21/18 5:23 PM, Walter Bright wrote:Yes. Good catch.In C, the way to do PIMPL is to write just a struct declaration: === s.h === struct S; === s.c === #include "s.h" struct S { ... }; === t.c === #include "s.h" struct T { S* pimpl; }; And the users of T cannot access anything in S. The straightforward equivalent in D is: === s.di === struct S; === s.d === struct S { ... } === t.d === import s; struct T { S* pimpl; }; But if you don't want to use .di files, such as when compiling all the files together with one command, then things get a bit awkward. D doesn't like: === s.d === public struct S; private struct S { ... } Making a public alias to a private struct doesn't work, as the struct's members are still accessible. I finally found a way: === s.d === public struct Spimpl { private S* pimpl; private alias pimpl this; } private struct S { ... } === t.d === import s; struct T { Spimpl* s;Don't you mean a nonpointer here?That's right. To avoid that, you'll need to organize the code for separate compilation. Of course, with Link Time Optimization, that goes out the window (and for C and C++, too).} and the contents of S are inaccessible to T, while type safety is maintained. There are no extra indirections - the alias this takes care of that. s.d can use s to directly access S's contents, but t.d cannot. https://github.com/dlang/dmd/blob/master/src/dmd/func.d#L229 https://github.com/dlang/dmd/blob/master/src/dmd/dinterpret.d#L151 C: http://wiki.c2.com/?PimplIdiom C++: http://en.cppreference.com/w/cpp/language/pimplWill this really work? I mean, if the compiler can see the implementation of S, even though it's private, can't it inline things and screw with the binary compatibility?I thought that was the main benefit of pimpl, to create a barrier which you know is going to separate the user from the implementation.In this case, I was looking to hide it from the user.
May 21 2018
On 5/21/18 11:44 PM, Walter Bright wrote:On 5/21/2018 2:41 PM, Steven Schveighoffer wrote:But link time optimization should be OK, because the idea is that you can link against different API-compatible objects, and then inlining is fine. The problem with inlining is that if you change the actual underlying implementation, the inlined function may do something horribly wrong compared to the new implementation. With LTO, the correct implementation is used for inlining. However, I'm not sure what the situation is with dynamic linking, but I would assume in order to keep everyone sane, LTO inlining is disabled for that situation.Will this really work? I mean, if the compiler can see the implementation of S, even though it's private, can't it inline things and screw with the binary compatibility?That's right. To avoid that, you'll need to organize the code for separate compilation. Of course, with Link Time Optimization, that goes out the window (and for C and C++, too).Well, the fact that you have the private implementation right there for the user to see, you might as well just do: public struct S { private: // impl } // in separate moudule: struct T { S* pimpl; } There's no need for the private struct. -SteveI thought that was the main benefit of pimpl, to create a barrier which you know is going to separate the user from the implementation.In this case, I was looking to hide it from the user.
May 22 2018
On 5/22/2018 6:27 AM, Steven Schveighoffer wrote:There's no need for the private struct.That is correct. But pragmatically, I wanted the public interface to be very small and easily inspected. The struct is very large with many dependencies and hard to inspect to see what is public and what is private.
May 25 2018