If the list is small enough that one doesn't mind the implicit temporary copy of joiners, this might seem a little more elegant, and still uses mesh (otherwise known as zip.
use List::MoreUtils qw(zip);
my @arrOfJoiners = ('!','*','?');
my @arrToJoin = (1,2,3,4);
my $string = join '', zip @arrToJoin, @{[ @arrOfJoiners, '' ]};
print "$string\n";
If the list of joiners is potentially huge, it would be better just to push an empty string to the end of the array:
use List::MoreUtils qw(zip);
my @arrOfJoiners = ('!','*','?');
my @arrToJoin = (1,2,3,4);
push @arrOfJoiners, ('') x ( @arrToJoin - @arrOfJoiners );
my $string = join '', zip @arrToJoin, @arrOfJoiners;
print "$string\n";