Can someone describe the precise coding tactic alluded to by author John Lakos in the following quote?
John Lakos:
More controversially, it is often better to have two copies of a struct—e.g., one nested/private in the .h file (accessible to inline methods and friends) and the other at file scope in the .cpp file (accessible to file-scope static functions) —and make sure to keep them in sync locally than to pollute the global space with an implementation detail.
The quote appears in the newer Lakos tome, Large Scale C++.
(The book is an updated treatment of the same subject matter as Lakos' earlier book, Large Scale C++ Software Design)
The quote is in Section 0.2, page 9.
If later chapters make it clear what Lakos is describing, I will return here and post an answer.
Meanwhile, I have become fascinated with understanding the quote, and my attempts to scan the book's table of contents and index for further clues have not yet yielded an answer.
Here is my own sample code for the problem that I imagine would be solved by the mysterious tactic:
// THE HEADER
namespace project
{
class OuterComponent
{
public:
inline int GetFoo()
{
return m_inner.foo;
}
int GetBar();
private:
struct InnerComponent
{
int foo = 0;
int bar = 0;
};
InnerComponent m_inner;
};
} // namespace project
Together with:
// THE CPP IMPLEMENTATION FILE
namespace project
{
namespace
{
/*
MYSTERY:
Per the book quotation, I can somehow add a "copy of InnerComponent" here?
And "make sure to keep them in sync locally"?
*/
// COMPILATION ERROR (see below)
int FileScopeComputation( OuterComponent::InnerComponent i )
{
return i.bar - 3;
}
} // namespace
int OuterComponent::GetBar()
{
return FileScopeComputation( m_inner );
}
} // namespace project
The above, of course, will not compile.
The error will be something like:
error: ‘struct project::OuterComponent::InnerComponent’ is private within this context
int FileScopeComputation( OuterComponent::InnerComponent i )
^~~~~~~~~~~~~~
The free function named FileScopeComputation cannot access InnerComponent, for reasons I well understand.
Relating the above Code to the Book Quote
Bringing this back to the Lakos quote, my thinking is that FileScopeComputation is one instance of what the quote calls "file-scope static functions".
One "obvious" solution to making the code compile is to move InnerComponent so that it is declared in the public section of OuterComponent.
However, I take my "obvious" solution to be guilty of (per the quotation) "[polluting] the global space with an implementation detail."
Therefore, my code seems to capture both: (a) the goal of the alluded tactic and (b) the unwanted pollution of one potential solution. So what is the alternative solution?
Please Answer Either or Both:
(1) Is there a way to make another copy of struct InnerComponent in the cpp file such that the field OuterComponent::m_inner remains private, the type OuterComponent::InnerComponent also remains private, and yet somehow the function FileScopeComputation has some way of doing something "equivalent" to accessing data on an instance of InnerComponent?
I've tried a couple bizarre things with casting, but nothing that looks even remotely worthy of recommending in a book. Meanwhile, it is my experience that things recommended by Lakos in books are all quite worthy of recommendation.
(2) Am I completely misreading what kind of scenario the quote applies to? If so, then what C++ software design problem is the quote actually referring to? What other problem involves "two copies of a struct... one in the h file.... the other in the cpp file"?
Update:
Based on the perceptive answer by dfri, the above code can indeed be made to compile with minimal changes, and such that:
- the field
OuterComponent::m_innerremains private - the type
OuterComponent::InnerComponentalso remains private - and yet the function
FileScopeComputationhas some way of accessing data on an instance ofInnerComponent
The header code gains one extra line of code:
...
private:
struct InnerUtil; // <-- this line was ADDED. all else is same as above.
struct InnerComponent
{
int foo = 0;
int bar = 0;
};
InnerComponent m_inner;
};
And the cpp file code becomes:
struct OuterComponent::InnerUtil
{
static int FileScopeComputation( OuterComponent::InnerComponent i )
{
return i.bar - 3;
}
};
int OuterComponent::GetBar()
{
return InnerUtil::FileScopeComputation( m_inner );
}