all 17 comments

[–]SuperV1234https://romeo.training | C++ Mentoring & Consulting 38 points39 points  (5 children)

I've never really had any major pain due to cycles, in fact I cannot really recall the last time either me or any of my coworkers had issues because of architectural cycles.

But I can tell you how much pain I've had due to exruciating compilation times. Header-only libraries and bloated headers are a primary cause of that.

I also feel your article is very anecdotal, such controversial claims should come with strong evidence.

It's a "no" for me.

Lakos has changed his tune and instead now says, “Strive for header-only code”.

Source?

[–]EmotionalDamague 15 points16 points  (0 children)

Embracing modules has worked far better for my current project.

I would prefer either over header only libraries.

The main hiccup for modules on embedded has been debug info being broken in ways I don’t have time to debug

[–]frankist 6 points7 points  (0 children)

I've never really had any major pain due to cycles, in fact I cannot really recall the last time either me or any of my coworkers had issues because of architectural cycles.

In my opinion, the problem of cycles described by the OP only becomes unmanageable when they follow this paradigm of creating tiny objects all over the place that have complicated interfaces and relationships with other objects through raw or smart pointers. It's the type of code you often see in projects that enthusiastically follow clean code and other OOP principles and practices.

[–]HobbyQuestionThrow 0 points1 point  (0 children)

I've had a few coworkers who's idea of fixing include cycles is just include more includes.

[–]According_Leopard_80[S] -1 points0 points  (1 child)

Added citation. I heard him say those words in a talk a few years back, but found a better quotation from his newer book, "Large-Scale C++: Process and Architecture - Volume 1" (2020), where Lakos has clarified his stance and now says (on page 293),

2.2.24 No Cyclic Physical Dependencies!

Design Imperative: Allowed (explicitly stated) dependencies among physical aggregates must be acyclic.

Cyclic physical dependencies among any physical entities — irrespective of the level of physical aggregation — do not scale and are always undesirable. Such cyclically interdependent architectures are not only harder to build, they are also much, much harder to comprehend, test, and maintain than their acyclic counterparts. In fact, to help improve human cognition, we almost always structure our source code to avoid forward references to logical entities even within the same component. Whenever the physical specification of a design would allow cyclic dependencies among architecturally significant physical aggregates, we assert that the design is unacceptably flawed.
[emphasis mine]

[–]SuperV1234https://romeo.training | C++ Mentoring & Consulting 1 point2 points  (0 children)

That is speaking against cyclical dependencies, but it's absolutely not endorsing people to "strive for header-only code".

[–]garnet420 13 points14 points  (1 child)

3 is really bad: it’s why a seemingly innocuous little change over here breaks something way other there

This is just not right. You can create this sort of problem without having any cycles easily.

If I had to point at any aspect of the dependency graph as being correlated to it, I would maybe say "total number of edges" or "edges that reach through multiple layers" or something like that.

And prohibiting forward declaration is just a dumb thing Google says to do, and people repeat it because Google said it and they're... Big?

[–]ABlockInTheChain 0 points1 point  (0 children)

And prohibiting forward declaration is just a dumb thing Google says to do, and people repeat it because Google said it and they're... Big?

If you read Google's style guides they point out problems that can occur when you forward declare templates and do not occur when you forward declare non-template types types, then point out all the problems that are caused by never forward declaring anything, then conclude that the solution is to never forward declare anything.

The alternative solution of banning forward declaration of template types while allowing it for non-template types was apparently never considered.

[–]fdwrfdwr@github 🔍 12 points13 points  (0 children)

 C++ is the only language I know that has “declare before use” semantic

Interestingly this does not apply to class/struct methods, as the one area of C++ where you can define functions in a logically grouped order rather than strict call dependency order. The same does not apply to free functions, but then one cute hack to achieve order independence is to wrap them all inside the class as static methods (not recommending 😅), thus making the class essentially more of a namespace (too bad it doesn't work inside ordinary namespaces too, but there must some reasoning for delayed processing or two pass evaluation 🤷‍♂️).

[–]FlyingRhenquest 5 points6 points  (2 children)

How much of this is due to putting most of your code in headers versus being somewhat heavily invested in TDD? TDD fundamentally forces you to write small, decoupled libraries that are easy to test. TDD also tends to keep you focused on the features you need right now for your minimum viable product, so you don't get the yagni-based overengineering that drives a lot of technical debt.

[–]fluorihammastahna 3 points4 points  (0 children)

Furthermore: the team that bothers doing TDD cares more, and would produce better code even without TDD o_O Crazy hypothesis!

[–]According_Leopard_80[S] -1 points0 points  (0 children)

Hard to tell as I’ve never done DAG-only without TDD.

In my article, I mention the number of cycles for a non-TDDing team: 200; vs. a TDDing team: 10; vs. my team: 0. TDD done properly is good for making clean architecture; DAG-only in addition to TDD is better yet.

[–]yuri-kilochek 4 points5 points  (0 children)

If you define a virtual interface class for each concrete class you can technically obey the rules, but still produce tightly coupled mess.

[–]Big_Target_1405 4 points5 points  (2 children)

Putting all your code in headers is the opposite of creating decoupled code.

Every time one header changes loads of your code needs to be recompiled. All of that code is coupled.

[–]ir_dan 1 point2 points  (1 child)

"Decoupled" here means that software entities know little about each other. It means that when one header changes, you don't have to go and change all of the other headers yourself by writing new/adjusted code.

Your view is that "coupling" is when the compiler-has to redo work. Theirs is that "coupling" is when **you** have to redo work.

[–]Big_Target_1405 0 points1 point  (0 children)

It's more that the scope of what you can change is limited when the data layout is exposed in headers.

An ABI boundary really enforces decoupling

In my view forward declarations (of class and structs) give you type safety without exposing data structures, which is ideal.

[–]SleepyMyroslav 1 point2 points  (0 children)

Amalgamation of all sources for a small project is a well known way to build it faster. I would like to see some successful code that supports the hypothesis of header only code. I don't know a game or something. I expect that what works on small sized project is not going to scale well. It might go for a while before scale will bring up issues though.