Hi please help me about why 'exit(value)' in this program cannot using correctly ?!


#1

In Thinking C++, the author builds a class using show whether object destructed.
but when I reproduced it, it falls down in infinitely loops until the system pops a window.
I read the codes, it used exit(1), when not clear the rest of object. but appearly it doesn’t run correctly.
so please tell my why , Thanks a lot!!!

in book:


error pic:

sourse code:(include original and question7 )
sourse code.rar (2.1 KB)


#2

Since I don’t know about the visibility of Twitter in China, I screencaptured this correct answer to your problem.

Take note also @Pharap and @drummyfish

(source: https://twitter.com/fasterthanlime/status/1189210610497720321/video/1)


#3

yep, I can read Twiiter through VPN. Thanks jonne! even through Im chaoing yet:joy:


#4

Insinuating that OOP is bad? You insult me sir.

Besides which the correct answer is to burn “Thinking in C++” and replace it with a better book.

All this using namespace std; and fputs is ugly outdated nonsense.


I can’t reproduce any infinite looping problems,
but I can see why you’re getting “All AutoCounter object not cleaned up”.

What’s actually happening is that AutoCounter::~AutoCounter() is never called because the AutoCounter object is never destroyed,
because you new the object and then never delete it!
I.e. a memory leak.


But really this whole class is inadequate anyway because it’s trying to use pointers (AutoCounter *) to track objects,
but objects don’t necessarily occupy the same address for the whole program.
They often get copied and moved around, e.g. pushed into a std::vector.

A proper implementation of a reference counter has to take creation, destruction, copy-construction, move-construction, copy-assignment and move-assignment into account.

E.g. something more like this:

#pragma once

// Needed for std::size_t
#include <cstddef>
#include <exception>
#include <iostream>

class ReferenceCounter
{
public:
	using counter_type = std::size_t;

private:
	counter_type count = 0;

public:
	ReferenceCounter()
	{
		// Console output used for debug purposes only
		std::cout << "A reference counter is initialised" << '\n';
	}

	~ReferenceCounter()
	{
		// Console output used for debug purposes only
		if(count > 0)
			std::cout << "A reference wasn't properly cleaned up" << '\n';
		else
			std::cout << "All references correctly cleaned up" << '\n';
	}

	counter_type get_count() const
	{
		return this->count;
	}

	void add_reference()
	{
		++count;
	}

	void remove_reference()
	{
		if(count == 0)
			throw std::exception("Attempt to remove a reference from an exhausted counter");

		--count;
	}
};

template< typename Type >
class ReferenceSentry
{
public:
	using type = Type;
	using counter_type = std::size_t;

private:
	static ReferenceCounter counter;

public:
	static const ReferenceCounter & get_counter()
	{
		return counter;
	}

private:
	bool has_reference = false;

private:
	void acquire_reference()
	{
		if(this->has_reference)
			throw std::exception("Attempt to reaquire reference");

		counter.add_reference();
		this->has_reference = true;
	}

	void release_reference()
	{
		if(!this->has_reference)
			throw std::exception("Attempt to rerelease reference");

		counter.remove_reference();
		this->has_reference = false;
	}

public:
	// Construction adds a reference
	ReferenceSentry()
	{
		this->acquire_reference();
	}

	// Destruction removes a reference
	~ReferenceSentry()
	{
		if(this->has_reference)
			this->release_reference();
	}

	// Copying acquires and releases as necessary
	ReferenceSentry(const ReferenceSentry & other)
	{
		if(other.has_reference)
		{
			if(!this->has_reference)
				this->acquire_reference();
		}
		else
		{
			if(this->has_reference)
				this->release_reference();
		}
	}

	// Moving behaves almost the same as copying for this type
	ReferenceSentry(ReferenceSentry && other)
	{
		if(other.has_reference)
		{
			if(!this->has_reference)
				this->acquire_reference();

			other.release_reference();
		}
		else
		{
			if(this->has_reference)
				this->release_reference();
		}
	}

	// Copying acquires and releases as necessary
	ReferenceSentry & operator=(const ReferenceSentry & other)
	{
		if(other.has_reference)
		{
			if(!this->has_reference)
				this->acquire_reference();
		}
		else
		{
			if(this->has_reference)
				this->release_reference();
		}
		return *this;
	}

	// Moving behaves almost the same as copying for this type
	ReferenceSentry & operator=(ReferenceSentry && other)
	{
		if(other.has_reference)
		{
			if(!this->has_reference)
				this->acquire_reference();

			other.release_reference();
		}
		else
		{
			if(this->has_reference)
				this->release_reference();
		}
		return *this;
	}
};

template<typename T>
ReferenceCounter ReferenceSentry<T>::counter = ReferenceCounter();

And as proof that this works (for at least the few cases that I’ve tried):

#include <iostream>

#include "ReferenceCounter.h"

class Resource
{
public:
	using sentry_type = ReferenceSentry<Resource>;

private:
	sentry_type sentry;

public:
	const sentry_type & get_sentry() const
	{
		return this->sentry;
	}
};

int main()
{
	using sentry_type = ReferenceSentry<Resource>;

	std::cout << sentry_type::get_counter().get_count() << '\n';
	{
		Resource resourceA;
		std::cout << sentry_type::get_counter().get_count() << '\n';

		Resource resourceB;
		std::cout << sentry_type::get_counter().get_count() << '\n';

		// Copying does not affect the counter because it merely copies resourceA
		resourceB = resourceA;
		std::cout << sentry_type::get_counter().get_count() << '\n';

		// Moving decreases the counter because it destroys resourceA
		resourceB = std::move(resourceA);
		std::cout << sentry_type::get_counter().get_count() << '\n';

		// resourceA is already destroyed, so trying to construct from it does not increase the counter
		Resource resourceC(resourceA);
		std::cout << sentry_type::get_counter().get_count() << '\n';
	}
	// The scope ends and all resources are destroyed
	std::cout << sentry_type::get_counter().get_count() << '\n';

	std::cin.get();
}

Showing that my ResourceSentry class can be used as a per-type counter that can be placed as a member object inside any class to enable reference counting.

Perhaps that’s a few features short of what AutoCounter is supposed to do,
but to do what AutoCounter is supposed to do without any flaws is somewhat more difficult because (as mentioned before) objects move around in memory so you can’t simply trace them by tracing their addresses, you’d need a way to generate unique object ids for every object, which gets pretty messy and frankly is quite an unrealistic requirement anyway.


In this modern day and age you don’t really need to be able to write something like that anyway.
std::shared_ptr, std::unique_ptr and std::weak_ptr are almost all you’ll ever need for managing resources.

You can even use them to wrap C library resources by providing a custom destructor.
For example, wrapping an SDL_Window:

std::shared_ptr<SDL_Window> createWindow(const char * title, int x, int y, int w, int h, Uint32 flags)
{
	return std::shared_ptr<SDL_Window>(SDL_CreateWindow(title, x, y, w, h, flags), SDL_DestroyWindow);
}

Which ensures that the window is destroyed when no shared pointers are still pointing to it.


I’m not saying you should never learn how to write a reference counter (I actually wrote one within my first year or so of learning C++),
but this book’s AutoCounter doesn’t seem to be a particularly good example of how to do it.


Also most sane programs don’t use std::exit.
These days errors are signalled by throwing exceptions.
std::exit is for when you’ve designed yourself into a hole and you can’t exit in a nice way.
(There’s a good SO answer about that here.)


#5

@Pharap, you have to admit that song is brilliant right down to the seg fault at the end

And, also, @79859899, I have to agree with Pharap here. “Thinking in C++” does not seem like a very good book.


#6

It’s well written, but terrible advice.

Unfortunately I can’t recommend any alternative books because I don’t read programming books very often (mainly because they tend to be very expensive and there’s a lot of good online resources available).

All I can suggest is https://www.learncpp.com/ and The C++ Programming Language,
and in the latter case I haven’t actually read it, I just trust Stroustrup to do a good job because his talks/llectures are usually very insightful.


#7

Brilliant. Indeed I’ve now stopped programming in C++ and OOP completely. C is still bloated, but it’s the closest we’ve ever gotten to the perfect language (sucks the least). I’d recommend starting by learning C, then learn C++ (so you can see why you shouldn’t use it), and also uninstall Windows.


#8

I thought you guys would appreciate the joke.

Nevertheless, let’s all resist the urge to debate C vs. C++ this time :wink:


#9

Mkay, I don’t wanna rant either, just pointing out why I can’t help with OP’s issue here.


#10

In fairness you should now by now that those kinds of ‘jokes’ just add fuel to the fire.

Without turning this into a debate, I will point out one thing about suckless:

Because dwm is customized through editing its source code, it’s pointless to make binary packages of it. This keeps its userbase small and elitist. No novices asking stupid questions.

In other words “get good or get lost”.

This is in direct conflict with the Pokitto motto of “there are no stupid questions”,
and personally I cannot support any group that doesn’t support helping novices,
even if they do recommend using tabs for indentation.


#11

Pharap, to me the best thing about this video was that it ended with a seg fault - the very kind of reason why OOP was invented in the first place. I thought it was rather clever.

I don’t see why we should arrive to any consensus on the matter. Its like cats and dogs, there are ardent fans for both, yet both have an equally good reason to exist.


#12

I think it’s a bit too subtle to decide what the intention was.

Essentially it’s Poe’s law in action.

Poe’s law is an adage of Internet culture stating that, without a clear indicator of the author’s intent, it is impossible to create a parody of extreme views so obviously exaggerated that it cannot be mistaken by some readers for a sincere expression of the views being parodied.

(And of course with the right tools it’s possible to segfault in any language. I once wrote a linked list class that caused a segfault and I never bothered to figure out why.)

Arguably no lifeforms have a reason for existing - they simply exist.
I think radio and television or television and the internet would be a better analogy.


I’d like to point out that I don’t think C should be enitrely discarded because it’s suitable for use in learning how to program (as are most languages) and some people enjoy using it,
but I don’t see any good argument for using it for serious development when more fully featured alternatives like C++ are available.


#13

Thanks to everyone.
I think I need a long time to understand them.
You know, my english is very poor as a Chinese.:joy::sob:

Hi Pharap, Ive started to using your advice to change my code style, your recommend website is setted my collect fold.
In China, the knowledge is considered less important than the relationship in society, so the most of books are cheaper than yours. I thinks that is a advantage :slight_smile:
recently, I brought some books in half price.:grin:


#14

in my opinion c++ is the best programing language.
if you programe for an powerfull device you can go full c++ no problem, this change when dealing with limited resources that MCU’s impose (flash, ram) you need to get your hands dirty and mix c code and assembly code with it, like if you make the screen update routine a c++ friendly you will get a maximum of 20 fps, another example when i want to use std::string in the kraken loader it eat more than 2Kb of the precious flash memory so i end up using c string manipulation instead.
in my opinion the ability to mix it with c and assembly is one of the strongest features of c++.


#15

If there is anything you don’t understand,
feel free to ask more questions.

In case you missed it:
The reason you get “All AutoCounter object not cleaned up” is because AutoCounter<int>::create uses new,
but you forgot to delete aip;.

Every new needs a delete!

Your English is better than my Chinese.

There’s also some useful resources here:

(The C++-specific links are here.)


@bl_ackrain

Sorry in advance for the text wall (I did actually cut a few things out to make it shorter).
Some of this is nitpicking, some of it is hopefully useful information.

Personally I wouldn’t claim any particular programming language is ‘the best’,
but C++ is probably my favourite for a multitude of reasons.

Assembly maybe, but C isn’t strictly necessary.
Anything C can do, C++ can do, and usually it’ll produce the exact same code when comparing like-for-like.

Compiled code doesn’t magically become larger just because it was written in C++.
What uses resources is particular language features (e.g. virtual functions, dynamic memory allocation),
so you simply avoid the more expensive features when dealing with low-level hardware.

In some cases C++ idioms can actually outperform C’s capabilities.
E.g. std::qsort (inherited from C) is notably slower than std::sort because templating can be better optimised than the type erasure incurred by using void *s and function pointers.
(Not to mention qsort can’t handle non-trivial types.)

(In case anyone wants to question this assertion about std::sort vs qsort: there’s some benchmarks here and multiple claims of std::sort being faster here.)

The majority of that is probably because it’s using new and delete under the hood,
which are almost certainly implemented in terms of malloc and free,
which are both pretty bulky functions.
Then there’s the cost of copying the string around.
Even if only pointer-based copies are done you still need the whole copy routine so you also get the logic that allocates a new buffer and copies the data across.

On top of which, I suspect you might have been actually copying the string around rather than moving it.
In most cases moving it should just be a matter of copying the pointer to the allocated buffer,
whereas copying it requires allocating a new buffer and doing a full copy.
(Most people aren’t used to writing std::move or don’t know when they could benefit by using it.)

But even then, I suspect mbed is doing the small string optimisation,
which adds to the size of complexity of the code as a tradeoff for better speed and fewer dynamic allocations.

The bottom line: strings are heavier and more complex than people tend to give them credit for.

I’d like to point out that I probably wouldn’t know all this if I had been learning another language.
One of the reasons I love C++ so much is because it helps me to appreciate just how much work goes on ‘under the hood’ - the work that most other languages are trying to hide from their users.

A little bit of trivia:

Although people tend to call them C strings,
null terminated strings weren’t actually created by C.
They came to be known as C strings simply because C was a popular language and it was the kind of string that C used.

Prior to the creation of C, null-terminated strings were used by the PDP family of computers and their various assembly languages (e.g. MACRO-10) and by BCPL, one of the languages that influenced C’s design.
(C was based on B, which was a modified version of BCPL. BCPL had a basic-like syntax, while B’s syntax is closer to pre-ANSI C.)