Queue for distributing tasks between threads
Separation of concerns
You have the members of your class as shared_ptrs this indicates that you want shallow copy assignment and construction. This is not something that you should concern yourself when designing the class.
I would recommend that you design your queue as non-copyable and non-assignable. Because assigning a queue makes no sense. If the user needs to pass a queue around, it is their responsibility to make a shared_ptr&lt;AsyncQueue&gt;. This has the benefit of making your implementation cleaner, but also performing better as your member variables will now get a better cache locality.
The big 5
Following the above the big 5 should now be:
        AsyncQueue(const AsyncQueue&amp; other) = delete;
        AsyncQueue(AsyncQueue&amp;&amp; other) = delete;
        AsyncQueue&amp; operator=(const AsyncQueue&amp; other) = delete;
        AsyncQueue&amp; operator=(AsyncQueue&amp;&amp; other) = delete;
        virtual ~AsyncQueue() = default;

(note delete instead of default). The move assignment and construction cannot be implemented as moving a mutex is not supported (for good reason).
Use make_shared
If you still insist on using shared_ptr, you should use make_shared to construct them because this will co-allocate the reference count with the object data. This saves some memory but more importantly it improves data locality.
Forwarding references
You're better off using forwarding references (also known as universal reference but forwarding reference is the new preferred name) for the push method. In fact I would go as far as to not provide push but instead provide emplace that constructs the object in place like this:
template&lt;typename... Args&gt;
void emplace(Args&amp;&amp;... args){
    {
        std::lock_guard&lt;std::mutex&gt; lock(*mutex);
        queue-&gt;emplace(std::forward&lt;Args&gt;(args)...);
    }
    cond-&gt;notify_one();
}

emplace() will construct the queued object in place using the arguments passed to emplace() as constructor arguments to avoid a copy. This way you leave the decision whether or not to move to your user.
Obj o;
asyncqueue.emplace(o); // Will copy construct.
asyncqueue.emplace(std::move(o)); // Will move construct.

// Will evaluate o*3 into a temporary and move from it. 
// Possibly eliding the copy.
asyncqueue.emplace( o * 3); 
asyncqueue.emplace(&quot;whatevz&quot;); // Will call constructor taking c-string.

Returning by move in pop
The reason you're crashing is that the local value you moved out of is destroyed when you leave the function scope. So you're moving from rubbish. Returning by r-value reference: Just don't. The compiler will likely perform (N)RVO and the copy will be optimized out if it is possible.
Condtional wait
You have an infinite conditional wait. It may be useful to have a functionality to &quot;sever&quot; the queue and release all threads waiting on jobs.
Typically you would have:
std::atomic&lt;bool&gt; still_running{true};

void stop(){ still_running = false; cond-&gt;notify_all();}

void push(T&amp;&amp;){
   if(!still_running) throw error(&quot;severed pipe&quot;);
   ...
}

void pop(){
   ...
   while(queue-&gt;empty() &amp;&amp; still_running) {
       cond-&gt;wait(lock);
   }
   if(queue-&gt;empty(){
       // Check for queue empty here, as still_running could
       // have been changed after we returned but we could still
       // return useful data.
       throw error(&quot;severed pipe&quot;);
   }
   ...
}

Also prefer the more concise way of waiting on a condition variable:
while(queue-&gt;empty()) {
    cond-&gt;wait(lock);
}

could be:
cond-&gt;wait(lock, [this](){ return !queue-&gt;empty();});

For reference, you might be interested in: my implementation of a similar class