Jump to content

  • Log In with Google      Sign In   
  • Create Account

King Mir

Member Since 11 Jun 2006
Offline Last Active Jul 02 2016 12:11 AM

#5293152 Is there a way to change initialisation order while keeping object layout ?

Posted by on 24 May 2016 - 12:11 AM

You could use one member unions to force class members not to be implicitly initialized in the constructor. Then in the constructor body you can use placement new to initialize that member late. Then, if member field is not trivially copyable, you also need to provide a copy constructor, destructor, and assignment operator.

 

It's ugly, so only use this technique for measurable performance gain.




#5291652 Non-trivial union

Posted by on 14 May 2016 - 09:26 PM

That's still not correct.

Essentially, if you have a non-trivial type in a union, you have to explicitly call constructors and destructors. Calling mObject in the initializer list does this for object, but then you cannot store an ArrayInfo there, without first calling the destructor. So instead, you should not construct either object in the member initializer expression list, instead using placement new in the constructor body. The code looks like this:

Type(const Type& that) :
    mKind(that.mKind)
{
    if ( that.mKind == eArray ) {     
      new (&mArray) ArrayInfo{that.mArray}; 
    }
    else {
      new (&mObject) ObjectInfo{that.mObject}; 
    }
}

(I also made this follow normal copy constructor semantics, which your code didn't do)

 

Likewise, you need to define a destructor, that will destroy the correct union member explicitly.




#5286587 Template-related compiler bug or... ?

Posted by on 12 April 2016 - 10:52 PM

I told you the rule that is applied, but I cannot cite the standard. If you require that level of rigor, I recommend reading the standard yourself.


#5286574 Template-related compiler bug or... ?

Posted by on 12 April 2016 - 10:03 PM

C++ template parameter deduction rules are smart, but there are limits. Essentially you ran into one. Each template function parameter is independently used to deduce the type of T. If different parameters disagree on the type of T, then the template is not considered.

 

C++ template type deduction rules are spelled out in the standard in excruciating detail, and compilers implement it exactly as specified; they cannot be any more or less smart about it because it would change the semantics of the language. While bugs are possible, you would need to be well versed in the C++ standard to differentiate between compiler bugs and C++ limitations.




#5286205 Why is my struct destructor not called?

Posted by on 10 April 2016 - 04:30 PM

 

 

You could explicitly invoke the dtor before you invoke the ctor again.
a.~A();
a = A();

This would be a bad idea in the general case. The assignment operator would assume that the object is in a valid state, but the destructor for most non trivial objects would put it in an invalid state.

 

Right, but the assignment is of new object (ie. a ctor is being assigned). It is more so a bad idea in the general case to cause memory leaks by stomping over an object without deallocating any memory its constructor allocates. (imho) So if you gotta call its dtor to clean up the object you want to overwrite, then just do it the hell with how awful the design is.. just get the job done if that is what it has come down to. (note: I am against cleanup or reset methods to do the job of a dtor just for the sake of avoiding explicitly calling a dtor)
 
If there is something to worry about with the assignment itself, then the class should have an operator overload for the assignment operator.
 
I just don't see the issue.

Edit: Though I should note your example of deleting internal buffers where the assignment operator expects valid buffers to be in place.. this is more of a case as to why you should not perform in place constructions. Or so it would seem to me.
There are many tools available and the assignment operator is just one. It might be a case where you should use a copy constructor, or a move constructor.

Personally, I use assignments for POD structures and rarely if every on a class. It is just bad design if you ask me.

 

Assignment to a valid object should not cause a memory leak. However, assignment to invalid object can cause problems. For example, if A here stored a pointer to dynamically allocated memory. In that case, calling the destructor would free the pointed to value, but the destructor probably would not set the pointer to null (because it does not need to leave the object in a valid state, and it would be an extra operation to null the pointer). Calling the assignment operator after the destructor would again try to free the same pointer, resulting in a double free. This problem occurs even if A does not have an explicit destructor, but happens to contain another object that manages heap memory like std::string.

 

Yes, classes that need an explicit assignment operator to have correct assignment semantics should use one. That goes without saying.

 

I disagree that there's anything bad about assigning to a class. This suggests to me that you probably don't use classes enough. Classes offer a layer of encapsulation of a concept that POD structs don't, so they are often preferable. But being an OOP class does not mean that it cannot make sense to be able to use assignment with it.




#5286089 Why is my struct destructor not called?

Posted by on 09 April 2016 - 09:58 PM

@Bitmaster...
 
I said pointers must be used because they are the language facility for managing object... "lifetime," for lack of a better word. Sorry you caught my post before I edited it to reflect my concerns about turning to "advanced concepts" not placement new specifically. The OP asked "Why am I not getting Destruct A [500]?!." The answer, in my opinion should have been something like "Because you need to call "delete a" on a A* or exit the scope which does not automatically happen when you assign." The assignment was addressed early but I reproach the jump to topics about optimization, stack, free-store, dynamic allocation, placement new all to avoid the most basic of C++ concepts: pointers and new+delete.
 
A user-defined type need not allocate any memory to have a non-trivial destructor but the "basic" way of taking control of an object's lifetime away from the automatic scoping is to use a pointer. Surely it is folly to be suggesting that pointers+new+delete = dynamic allocation = free store = slow = much typing = avoid, avoid, avoid?? The OP wanted to dynamically destroy (ie before end of scope)  why are we trying to concoct a static allocation??
 
The OP clearly hit a gotcha in expecting the destructor to be called within the scope of the function. Pointers are necessary, irrespective of the language facility, allocator, container or custom class used to implement RAII / reduce typing / avoid arrow dereferencing / avoid free-store...etc. C/C++ programmers MUST know that they are responsible for their object's lifetime from the moment they declare a variable. This is the C/C++ way.
 
Automatic initialization+destruction and support structures exist to allow the programmer to delegate that responsibility not be relieved of it. "Thou shalt assume responsibility for this object's lifetime, on my behalf, from point A (e.g initialization) to point B(e.g end of scope or reset()). Placement new, manual destructor call, and all the other expert "implementations" of handling object lifetime must not change "Good Practice" in using variables/pointers/objects/references especially in main() or any other user-level function.
 
My example was meant to demonstrate the awareness a programmer should have of object lifetime, with particular respect to end of scope which is why I showed the unique_ptr example as one way of delegating responsibility and avoiding manual new+delete. "...an emphatic no." to the use of free store is a bit extreme. If one prefers to dabble in stack-only allocation then they are free to place "new" there, even for unique_ptr.
 
It is certainly interesting to explore the advanced concepts related to object allocation but all that "expert" knowledge belongs in "encapsulation." "General Programming" should be a place to exemplify good coding practices, moreso than "For Beginners" IMO. Bjarne Stroustrup and most of the "top brass" regularly bemoan the tendency of experts to turn basic questions into in-depth analyses of advanced language concepts instead of demonstrating the virtues and wonders of simplicity.
 
"The Pointer" is basic C/C++. It is what clearly defines the languages from Java, C# and others. The years have afforded us facilities to delegate object lifetime management but it remains our greatest advantage that we can say and aspire to guarantee that a particular scope behaves as it appears (as long as we don't go fighting the language with "expert" tricks).

Using pointers may be basic C++, but so is not using them when you don't need them. In the OP, "use dynamic allocation" is the wrong advice to give. If you want to talk about tools avalible to control when an object is destroyed, the foremost to consider is not dynamic memory, it's using an extra set of braces to contain the variable.


#5275686 Multithreading Nowadays

Posted by on 14 February 2016 - 07:40 PM

I think at least for games, another significant problem with hyperthreading is that many workloads don't scale perfectly with core count. See https://en.wikipedia.org/wiki/Amdahl's_law for one cause of poor scaling.

 

That is, even if you compare say two core performance to four core performance without any hyperthreading, then the four cores probably won't be exactly double the speed of two cores. It might get say 1.8 times faster instead.

 

This means that the performance benefit from hyperthreading has to be higher than the overheads from using more threads, if it's going to actually improve performance.

Even without hyperthreading, you often have a measurable benefit from using more threads than cores because there's always a busy thread to swap in. Especially when your application has different workloads, instead of always doing the same computation for different data. In a way, Hyperthreading is just a way to make this more efficient through hardware. However, with hyperthreading or without, the optimal number of worker threads is something you can often measure directly; knowing about hyperthreading just explains the results.




#5274852 Constructor + putting into an array

Posted by on 08 February 2016 - 08:42 AM

Also consider using std::array in C++11. It's an array that behaves more like a normal type, specifically without implicit pass by reference.




#5272398 C++ exceptions

Posted by on 23 January 2016 - 03:16 PM

 

You do have to think about errors in C++ without exceptions, because the same exact code can fail regardless of your error handling strategy.

You missed the point of my example. If we look at a function with this signature:
void Queue::Push(const T&);
In C++-without-exceptions, we know already that operation cannot fail.
In C++, we have to ask the author of that function (via documentation/comments) if it potentially throws any exceptions, and trust the docs/comments are correct.
 
In C++-without-exceptions, if that operation were to have a failure case, then the API would have to reflect it, such as:
bool Queue::TryPush(const T&);

 

Looking at the return type vs looking a few lines higher in a comment documenting the function is not a big difference, IMO.

 

Also, queue push, regardless of the prototype, can fail. It can fail if it fails to allocate memory, and it could potentially have a ring buffer implementation, that would run out of queue space sooner. The queue has to do something to deal with that. There are several options, even if the return value is void: it could just abort the application. It could require you to check if the queue has room before every push. It could set a flag on failure that you would need to explicitly check, either global or as part of the queue object. It could even call longjmp, and I have seen C libraries that will do this for errors. But it can't never fail because pushing an object on a queue is inherently an operation that can fail.




#5272081 C++ exceptions

Posted by on 20 January 2016 - 08:06 PM

 

 

The core problem I think is that practically anything can throw an exception, including basic things like + and =.  You can handle it correctly, but it left me withe the impression that you're basically implementing data base transactions in a programming language that has no support for data base transactions.
I don't think I ever want to go there.

You're talking about the difficulty in writing an exception-safe generic library. If you're not writing a library, but an application where you know which operations can fail, the problem becomes much easier. You don't need to worry about + or = failing, unless they might do so for the particular classes you use.
Furthermore, its not fair to compare code that dutifully handles every exceptional case, to code that does not account for errors.

 

The same, or a similar form of that criticism applies to application code.
In a C++ without exceptions, you don't often have to think about errors -- if Queue::Push(T) has a void return value, then it always succeeds.
On a C++ with exceptions, even if Queue::Push(T) is void, you've got to go refer to some comments or documentation to find out whether it has failure conditions. Even if it doesn't have failure conditions, you're forced to spend time checking documentation to see if it does or not.

 

 

You do have to think about errors in C++ without exceptions, because the same exact code can fail regardless of your error handling strategy. However, I concede that exceptions make it easier to overlook an error condition when refactoring code, which in turn requires more diligence in making sure that you don't.

 

This particular criticism would be nullified if throw specifiers worked and were mandatory -- e.g. you weren't allowed to throw unless that exception was listed in the signature, such as:
void Queue::Push(T) throws(QueueFullException)
Most of my hatred of C++ exceptions boils down to this invisibility in the API declaration smile.png I'd be much happier for them to be a common part of C++ if they had to be listed like this. This would also solve the "viral" nature of exceptions.
e.g. say I write a function void Missile::Target(Pawn), which internally calls Queue::Push but has no try/catch internally -- in this situation, Push's throwing status virally infects my Target function. In these situations, it's very easy for me to forget to add to the documentation that my function may throw a QueueFullException. Or worse, I may forget to update the documentation later when I do start handling this exception. If the compiler actually verified that your throw specifiers were correct, then it would remove the need for extra (easily incorrect) documentation, and ensure that API declarations always listed all possible errors produced by a function.
 
C# pretty much uses this solution -- it doesn't force you to list the exceptions in a specifier like this, but the compiler can figure out the full list of potentially thrown errors for you, so I'm happy to use exceptions in C#.

The problem with this approach is that it doesn't play well with templates. And choosing between using exception specifications, and templates, people choose templates.




#5271614 C++ exceptions

Posted by on 17 January 2016 - 06:09 PM

 

I agree that exceptions aren't necessarily the best means of error handling, but in some cases they meet requirements that return-code style handling simply does not.
I recommend that you read "Exceptional C++". The entire book is about how to write exception-safe code.

It opened my eyes on how complicated it is to write code that can safely continue after an exception.

 

The core problem I think is that practically anything can throw an exception, including basic things like + and =.  You can handle it correctly, but it left me withe the impression that you're basically implementing data base transactions in a programming language that has no support for data base transactions.

I don't think I ever want to go there.

 

You're talking about the difficulty in writing an exception-safe generic library. If you're not writing a library, but an application where you know which operations can fail, the problem becomes much easier. You don't need to worry about + or = failing, unless they might do so for the particular classes you use.

 

Furthermore, its not fair to compare code that dutifully handles every exceptional case, to code that does not account for errors. You would need to it to code that dutifully makes sure to call a callback function or propagate an error code on any operation like + that might overflow, underflow, or otherwise fail. Removing exceptions doesn't remove the need to make your code robust to errors. Exceptional C++ would be even more complicated if it weren't for exceptions.




#5267122 Your development workflow

Posted by on 20 December 2015 - 04:00 AM

For my last project, which was not a game and did not have a GUI, my process was this:

 

1) Pick something to work on; a new feature or a known defect on the TODO list. Possibly add the feature to the list

2) If it's a new feature, write a test for it. mark the test on the todo list for the feature.

3) Write code

4) Run all regression tests. Tests report changes, so I verify that any changes are consistent with the change.

5) Fix any crashes in regression tests. Non crashes may be logged as defects instead, and the test updated with the new expected behavior.

6) Verify the feature with tests written for it in step 2.

7) Add working feature test to regression tests.

6) Remove the feature from the to do list, and add it to the supported features list if applicable.

 

This project was suitable for this kind of workflow because everything was deterministic and all IO is file based or standard streams. So the tests were just for this input file diff the expected output with what's actually produced. Part of the motivation was to make it easy to work on and off on the project. As long as all the regression test pass It was fairly easy to get back into it, by picking any task on the todo list.




#5266365 Looking for lightweight GUI/Widget libary for SDL

Posted by on 14 December 2015 - 04:48 PM

I've had excellent success with IMGUI:

https://github.com/ocornut/imgui

Thank you, this looks like exactly what I'm looking for.




#5265757 Looking for lightweight GUI/Widget libary for SDL

Posted by on 10 December 2015 - 12:19 PM

Could somebody recommend a GUI library built on top of SDL 2?
In need the following:

  • Must be open source, including all dependencies.
  • Lightweight, with few or no dependencies.
  • Support basic gui widgets including table layouts, buttons, labels, and scrollable widgets.
  • Need to be able to add custom widgets.
  • Built on top of SDL 2. Might be able to use SDL 1 or OpenGL too.
  • Must not rely on dynamically linked libraries. I need to compile and statically link all dependencies.
  • C or C++



#5261690 char[32] as function parameter

Posted by on 11 November 2015 - 05:08 PM

I would recommend using std::array to avoid all the messiness with decay to pointer. std::array is not like std::string, because it is a fixed size array that does not dynamically allocate memory.

As for std::array being owning, there are proposals to add array_view to the standard, which is a non owning range. This has the primary benefit of allowing the various ways that a sequence of objects are represented in c++ to converted to the same interface. But if you don't need a vector of 32 elements to be acceptable input then a reference to std::array<char, 32> is sufficient.

The syntax for array passing is crazy in C++ but this will do allow you to do this:

template <size_t charCount>
void stringCopy(char (&output)[charCount], const char* source)
{
}
I highly recommend you use a template on the array parameter here because this function now works with any sized arrays.

In this case the size is known, so you don't need the template:
void stringCopy(char (&output)[32], const char* source)
{
}
But I still recommend using std::array.




PARTNERS