Choosing things from JUCE over Standard Library (or vice versa)


#1

Hello everyone,

I often wonder whether I should be using something from the Standard Library or from JUCE. There are of course a lot of things that JUCE has that the Standard Library doesn't, but there are also a lot of things that work very similar between the two. I'm curious if any of you can tell me some cases where you would choose one over the other.

For example, when would you choose a standard container over a JUCE container? Or, when would you choose one of the standard smart pointers over a JUCE smart pointer?

I don't feel like I know enough about anything so I can't always make wise decisions when it comes to choosing between two similar things. My general rule (from listening/reading remarks from a lot of experts) is to use the standard library if it has what I need, but I would definetely want to choose the right tool for the job. I really appreciate any discussion on this topic and I'd really appreciate if you share any use cases you've come across where one was a better choice then the other.


#2

I think 90% of the time they are equivalent.  

Aside from the bit obvious difference in interfaces, the two main containerss have an important difference:

  • juce::Array<> moves your objects with memcpy (actually realloc). ¬†
  • std::vector moves them with a move (or a¬†copy constructor).¬†

The notes for realloc on a C++ site online are informative: 

Because reallocation may involve bytewise copying (regardless of whether it's to expand or to contract), only the objects of TriviallyCopyable types are safe to access in the preserved part of the memory block after a call to realloc.

Some non-standard libraries define a type trait "BitwiseMovable" or "Relocatable", which describes a type that

  • does not have to register with an observer that maintains a pointer to this object (e.g. nodes of a list or a tree)
  • has no members of pointer type that point to members of the same object

Objects of such type can be accessed after their storage is reallocated even if their copy constructors are not trivial.

I had this headache last year sometime: http://www.juce.com/forum/topic/value-and-listeners

Maybe someone else has a better handle than I on the merits of the smart pointer options....


#3

I am now ignoring the Juce smart pointers after seeing that ScopedPointer leaks memory when doing assignment with the = operator. (And possibly under other circumstances.) That probably works as intended (within the constraints of C++98 etc) but the behavior was too unexpected after using C++11's std::unique_ptr which does the right thing.

I also haven't seen much reason to use the Juce container classes unless their use is required by Juce itself. So, I usually wouldn't do std::vector<String> since Juce uses StringArray in its own APIs. However, I wouldn't use for example Array<double> instead of std::vector<double>.


#4

Leaks memory? Huh? Under what circumstances?

Obviously ScopedPointer is limited by the constraints of C++98 but although I can think of ways in which it could accidentally delete and clear a pointer, I've never actually seen a situation where it would leak.

But yes - I'd also encourage use of unique_ptr and other modern C++11 containers if you can! Things like vector actually used to have terrible performance on some platforms, which is why I created my own classes, but modern compilers have fixed all those problems now. One day it'll be great to migrate the whole library over to unique_ptr!


#5

I'll try to come up with a simple test case for you.

But if my memory serves right, the problem occurred when having a construct like this as a member variable : std::vector<ScopedPointer<Component>>. It was exceedingly easy to get those Juce memory leak detector assertions with that at program run end. Just changing that to std::vector<std::unique_ptr<Component>> solved that instantly. (Obviously the vector was cleared, elements were reassigned etc during the program run.)


#6

Damn, not getting a memory leak this time but rather just a crash when using std::vector's push_back with a ScopedPointer<Component>! Curiously, std::vector::emplace_back does seem to work...


#7

Anyway, actually the ScopedPointer assignment memory leak is trivially reproducible with code like :


    m_but = ScopedPointer<TextButton>(new TextButton("Create sliders"));
    m_but->setBounds(0, getHeight() - 20, 100, 20);
    addAndMakeVisible(m_but);
    m_but->addListener(this);
    m_but = ScopedPointer<TextButton>(new TextButton("Create sliders 2"));
    m_but->setBounds(100, getHeight() - 20, 100, 20);
    addAndMakeVisible(m_but);
    m_but->addListener(this);

.Where m_but is a member variable ScopedPointer<TextButton> m_but. That it's not working correctly can be seen right away when the program is run, 2 buttons are visible. (And Juce does complain of a memory leak in the end too.) But if the "smart" pointer worked correctly, the first created button would have been already destroyed with the code above. (And that does indeed work like that with std::unique_ptr). 

Using Visual Studio 2015 RC here. (These same problems happened with VS 2013 too.)


#8

Is there a std::library equivalent to ReferenceCountedObject?


#9

You're creating a new scopedpointer, of course that's not going to work.
(scopedpointers should never be allocated on the stack, always on heap, ie member/function variables)

You should do this instead:

m_but = new TextButton("Create sliders");

#10

Well, I didn't yet test if that works but even if it does, I'd find that behavior very odd compared to std::unique_ptr and I wouldn't want to do that. The code would look like I am assigning a raw pointer to a smart pointer.

 

edit : OK, doing it like you suggested works correctly but I am still not going to use Juce's ScopedPointer. I find the semantics of std::unique_ptr clearer.


#11

http://en.cppreference.com/w/cpp/memory/shared_ptr

?


#12

I use the juce containers most of the time, mainly for consistency and because they have a more concise interface.

Is there a std::library equivalent to ReferenceCountedObject?

STL has a non-intrusive reference counting pointer, std::shared_ptr. I'm actually a big fan of this, mainly because it also has the weak_ptr implementation. STL shared_ptr & weak_ptr are atomic while the juce::WeakReference is not.

Using these means you can reference count any object although you have to create other shared_ptrs from a shared_ptr (unless you inherit from enable_shared_from_this<Type>.

Both have their advantages but it's a bit of a pain to mix the two.


#13

Yeah, the way you were using it didn't make much sense at all. If you use it in the normal way then there's really not much difference between ScopedPointer and unique_ptr, except in a few edge-cases where move operators and comparisons are involved.


#14

Well, I just find it odd the ScopedPointer should be used like 
m_but = new TextButton("Create sliders");

I don't like that. That code doesn't even compile with std::unique_ptr. And of course my example above was a contrived one. There are more sensible situations where assignments would be done and the expectation is that the old object held by the smart pointer is deleted.


#15

the expectation is that the old object held by the smart pointer is deleted.

But that's exactly how ScopedPointer works.. (?)


#16

Yes, once you know that the operator = must be used with a raw pointer. That wasn't expected after using std::unique_ptr where it's done like : m_but=std::unique_ptr<TextButton>(new TextButton("Foo")); or preferably m_but=std::make_unique<TextButton>("Foo");

This doesn't even compile with std::unique_ptr, on purpose : m_but=new TextButton("Foo");

I can see that you've thought with ScopedPointer that taking in a raw pointer in the constructor and operator = is a convenient thing to have, as would be the automagical conversion of ScopedPointer to a raw pointer. IMHO that isn't good, but that's just my opinion.


#17

Your example is starting to grasp at straws, but this url may help explain your issue: http://stackoverflow.com/questions/3283778/why-can-i-not-push-back-a-unique-ptr-into-a-vector

Seeing that std::vector is finicky when combined with a smart pointer, and that you're reinventing the concept of a container that owns its objects - you could well make use of juce::OwnedArray to KISS. (?)


#18

I can see why there's a difference in the way the two deal with assignment of a new pointer  But, ignoring the fact that one came before the other - I don't know which is better? Is requiring the explict conversion somehow safer?


#19

IMHO, people should use std stuff if they don't mind c++11 and Jules 

should stop doing replacement classes for those, mark its classes deprecated and use c++11 in its code

 

my 2 cents

 


#20

I'd love to use only C++11

Unfortunately there are valid reasons why some people can't do that, e.g. needing PACE support, needing to use old 3rd party libraries that won't link in VC2013, etc.